/* Minification failed. Returning unminified contents.
(6837,64-65): run-time error JS1014: Invalid character: `
(6837,66-67): run-time error JS1193: Expected ',' or ')': {
(6837,72-73): run-time error JS1014: Invalid character: `
(6837,73-74): run-time error JS1004: Expected ';': )
(6838,10-11): run-time error JS1004: Expected ';': )
(6849,1-2): run-time error JS1002: Syntax error: }
(6851,20-21): run-time error JS1004: Expected ';': {
(6871,2-3): run-time error JS1195: Expected expression: )
 */
DayEventLayoutEngine = function (getEventObjFunc, theOption) {
    this.option = {
        isAdjustFromRightToLeft: true,
        isAdjustFromLeftToRight: true,
        isLogEnabled: false,
        isDumpEnabled: false,
        getEventObjFunc: getEventObjFunc
    };
    if (theOption) {
        for (var prop in theOption) {
            this.option[prop] = theOption[prop];
        }
    }
    this.adjustedStepCnt = 0;
};

DayEventLayoutEngine.prototype = {
    run: function (dayEvents) {
        if (!dayEvents || !dayEvents.length) return;
        this.log('[DayEventLayoutEngine] start');
        var containerHost = this.groupContainer(dayEvents);
        console.log('[DayEventLayoutEngine] group result', containerHost);
        var thisOjb = this;
        for (var containerId in containerHost) {
            var containerEvents = containerHost[containerId];
            var sortedDayEvents = containerEvents.sort(thisOjb.compareDayEvent);
            var groupedDayEvents = thisOjb.groupDayEvents(sortedDayEvents);            
            for (var groupIndex = 0; groupIndex < groupedDayEvents.length; groupIndex++) {
                thisOjb.arrangeDayEvents(groupedDayEvents[groupIndex]);
            }
        }
        if (this.option.isDumpEnabled) {
            this.dumpEvents(dayEvents);
        }
    },
    groupContainer: function (dayEvents) {
        var containerHost = {};
        for (var eventIndex = 0; eventIndex < dayEvents.length; eventIndex++) {
            var dayEvent = dayEvents[eventIndex];
            this.formatEvent(dayEvent);
            if (!containerHost[dayEvent._containerId]) {
                containerHost[dayEvent._containerId] = [];
            }
            containerHost[dayEvent._containerId].push(dayEvent);
        }
        return containerHost;
    },
    groupDayEvents: function (dayEvents) {
        var groupedDayEvents = [];
        var currentGroup = [];
        for (var eventIndex = 0; eventIndex < dayEvents.length; eventIndex++) {
            var dayEvent = dayEvents[eventIndex];
            var hasFoundGroup = false;
            for (var eventIndexInGroup = 0; eventIndexInGroup < currentGroup.length; eventIndexInGroup++) {
                if (this.isDayEventOverlap(dayEvent, currentGroup[eventIndexInGroup])) {
                    currentGroup.push(dayEvent);
                    hasFoundGroup = true;
                    break;
                }
            }
            if (hasFoundGroup) {
                continue;
            } else {
                currentGroup = [dayEvent];
                groupedDayEvents.push(currentGroup);
            }
        }
        return groupedDayEvents;
    },

    arrangeDayEvents: function (dayEvents) {
        var sortedDayEvents = dayEvents;
        var dayEventsCnt = sortedDayEvents.length;
        var columnCnt = 1;
        var columnGroups = [];
        var wideEvents = [];
        if (dayEventsCnt > 0) {
            sortedDayEvents[0]._column = 1;
            var firstColumnGroup = [sortedDayEvents[0]];
            columnGroups.push(firstColumnGroup);
            for (var i = 1; i < dayEventsCnt; i++) {
                var dayEvent = sortedDayEvents[i];
                var columnGroupIndex = 0;
                for (; columnGroupIndex < columnGroups.length; columnGroupIndex++) {
                    var isDayEventOverlapWithThisColumn = false;
                    var dayEventsAtPreviousColumn = columnGroups[columnGroupIndex];

                    for (var j = 0; j < dayEventsAtPreviousColumn.length; j++) {
                        if (this.isDayEventOverlap(dayEvent, dayEventsAtPreviousColumn[j])) {
                            isDayEventOverlapWithThisColumn = true;
                            dayEvent._preEvent = dayEventsAtPreviousColumn[j];
                            break;
                        }
                    }
                    if (!isDayEventOverlapWithThisColumn) {
                        break;
                    }
                }
                dayEvent._column = columnGroupIndex + 1;
                if (columnGroups.length > columnGroupIndex) {
                    columnGroups[columnGroupIndex].push(dayEvent);
                } else {
                    var columnGroup = [dayEvent];
                    columnGroups.push(columnGroup);
                }
            } // end for
            columnCnt = columnGroups.length;
            var sortDayEventsByColumn = dayEvents.sort(this.compareDayEventByColumn);

            for (var i = 0; i < dayEventsCnt; i++) {
                var dayEvent = sortedDayEvents[i];
                dayEvent._columnSpan = 1;
                dayEvent._columnCnt = columnCnt;
                for (var j = dayEvent._column; j < columnGroups.length; j++) {
                    var dayEventsAtNextColumn = columnGroups[j];
                    var isDayEventOverlapWithThisColumn = false;
                    for (var k = 0; k < dayEventsAtNextColumn.length; k++) {
                        if (this.isDayEventOverlap(dayEvent, dayEventsAtNextColumn[k])) {
                            isDayEventOverlapWithThisColumn = true;
                            dayEvent._nextEvent = dayEventsAtNextColumn[k];
                            break;
                        }
                    }
                    if (!isDayEventOverlapWithThisColumn) {
                        dayEvent._columnSpan++;
                    } else {
                        break;
                    }
                }
                if (dayEvent._columnSpan > 2) {
                    wideEvents.push(dayEvent);
                }
            } // end for
        }

        if (this.option.isAdjustFromRightToLeft && wideEvents.length > 0) {
            this.adjustedStepCnt = 0;
            var thisObj = this;
            this.log('--start adjust');
            wideEvents.forEach(function (event) {
                if (event._nextEvent && event._nextEvent._preEvent === event) return;
                thisObj.log('adjust [' + event._title + '] from right to left');
                var columnCnt = event._column + event._columnSpan - 1;
                var averageColumnSpan = Math.ceil(columnCnt / event._column);
                thisObj.adjustFromRightToLeft(event, columnGroups, averageColumnSpan);
            });
            this.log('--finish adjust with count ' + this.adjustedStepCnt);
        }

        return {
            columnCnt: columnCnt,
            dayEvents: sortDayEventsByColumn
        };
    },

    adjustFromRightToLeft: function (event, columnGroups, averageColumnSpan) {
        this.log('adjust [' + event._title + '] from right to left');
        if (!event._preEvent) {
            this.log('no pre event');
            return;
        }
        if (event._columnSpan < 3) {
            this.log('column span < 3');
            return;
        }

        var preEvents = this.findPreEvents(event, columnGroups);
        for (var i = 0; i < preEvents.length; i++) {
            var preEvent = preEvents[i];
            this.adjustFromRightToLeftInner(event, preEvent, columnGroups, averageColumnSpan);
        }
    },

    adjustFromRightToLeftInner: function (event, preEvent, columnGroups, averageColumnSpan) {
        var columnGap = 0;
        var isFirstPreEvent = false;
        // if the first pre event, means the event itself hasn't been adjusted
        if (event._preEvent === preEvent) {
            isFirstPreEvent = true;
            columnGap = Math.max(event._columnSpan - averageColumnSpan, 0);
        } else {
            isFirstPreEvent = false;
            columnGap = Math.max(event._column - preEvent._column - preEvent._columnSpan, 0);
        }

        this.log('start adjust [' + event._title + '], pre [' + preEvent._title + ']'
            + ', averageColumnSpan ' + averageColumnSpan
            + ', column span ' + event._columnSpan
            + ', column gap ' + columnGap);

        if (columnGap === 0) return;

        var fromColumn = preEvent._column + preEvent._columnSpan;
        var targetColumn = fromColumn + columnGap;
        var reachedColumn = fromColumn;
        var hasOverlap = false;
        this.log('start try adjust [' + preEvent._title + '] from ' + fromColumn + ' to ' + targetColumn);

        for (; reachedColumn < targetColumn; reachedColumn++) {
            var reachedColumnGroup = columnGroups[reachedColumn - 1];
            if (this.isDayEventOverlapWithGroup(preEvent, reachedColumnGroup)) {
                hasOverlap = true;
                break;
            }
        }
        this.log('try finished, adjust [' + preEvent._title + '] from ' + fromColumn
            + ' to ' + reachedColumn);

        var freeColumnSpan = reachedColumn - fromColumn;
        if (freeColumnSpan > 0) {
            // Only adjust event itself when ajdust first pre event
            if (isFirstPreEvent) {
                this.removeItemFromGroup(columnGroups[event._column - 1], event);
                targetColumn = event._column + freeColumnSpan;
                columnGroups[targetColumn - 1].push(event);
                event._column = targetColumn;
                event._columnSpan -= freeColumnSpan;
                this.adjustedStepCnt++;
            }
            preEvent._columnSpan += freeColumnSpan;
            this.adjustedStepCnt++;
        }

        if (hasOverlap) {
            this.log('adjust from right to left, meet overlap, stop');
            if (event._columnSpan > averageColumnSpan + 1) {
                this.log('[' + event._title + '] is too wide, try adjust from left to right');
                if (!this.option.isAdjustFromLeftToRight) {
                    this.log('adjust from left to right disabled');
                    return;
                }
                var rightColumnCnt = this.findRightColumnCnt(event);
                var toBeAdjustedColumnCnt = rightColumnCnt + 1;
                var averageColumnSpan2 = Math.floor((event._columnCnt - event._column + 1) / toBeAdjustedColumnCnt);
                this.adjustFromLeftToRight(event, columnGroups, averageColumnSpan2);
            }
        } else if (isFirstPreEvent) {
            // continue adjust pre event recursively
            this.adjustFromRightToLeft(preEvent, columnGroups, averageColumnSpan);
        }
    },

    adjustFromLeftToRight: function (event, columnGroups, averageColumnSpan) {
        this.log('ajudst from left to right', event);
        if (!event._nextEvent) {
            this.log('no next event');
            return;
        }
        if (event._columnSpan < 3) {
            this.log('column span < 3');
            return;
        }

        var nextEvent = event._nextEvent;
        var columnGap = Math.max(event._columnSpan - averageColumnSpan, 0);
        this.log('start adjust, averageColumnSpan ' + averageColumnSpan
            + ', column span ' + event._columnSpan
            + ', column gap ' + columnGap);

        var fromColumn = nextEvent._column - 1;
        var targetColumn = fromColumn - columnGap;
        var reachedColumn = fromColumn;
        this.log('start try adjust [' + nextEvent._title + '] from ' + fromColumn
            + ' to ' + targetColumn);

        for (; reachedColumn > targetColumn; reachedColumn--) {
            var reachedColumnGroup = columnGroups[reachedColumn - 1];
            if (this.isDayEventOverlapWithGroup(nextEvent, reachedColumnGroup)) {
                break;
            }
        }

        this.log('try finished, adjust [' + nextEvent._title + '] from ' + fromColumn
            + ' to ' + reachedColumn);

        var freeColumnSpan = fromColumn - reachedColumn;
        if (freeColumnSpan === 0) return;

        targetColumn = nextEvent._column - freeColumnSpan;
        event._columnSpan -= freeColumnSpan;
        this.removeItemFromGroup(columnGroups[event._nextEvent._column - 1], nextEvent);
        columnGroups[targetColumn - 1].push(nextEvent);
        nextEvent._column = targetColumn;
        nextEvent._columnSpan += freeColumnSpan;

        this.adjustedStepCnt += 2;

        // continue adjust next event recursively
        this.adjustFromLeftToRight(nextEvent, columnGroups, averageColumnSpan);
    },

    formatEvent: function (dayEvent) {
        var eventObj = this.option.getEventObjFunc(dayEvent);
        dayEvent._title = eventObj.title;
        dayEvent._startMinute = eventObj.startMinute;
        dayEvent._endMinute = eventObj.endMinute;
        dayEvent._containerId = 'c-' + eventObj.containerId;
        dayEvent._preEvent = null;
        dayEvent._nextEvent = null;
    },

    compareDayEvent: function (dayEvent1, dayEvent2) {
        var result = dayEvent1._startMinute - dayEvent2._startMinute;
        if (result == 0) {
            result = dayEvent2._endMinute - dayEvent1._endMinute;
        }
        return result;
    },

    compareDayEventByColumn: function (dayEvent1, dayEvent2) {
        return dayEvent1._column - dayEvent2._column;
    },

    isDayEventOverlap: function (dayEvent1, dayEvent2) {
        var isOverlap = (dayEvent2._startMinute > dayEvent1._startMinute && dayEvent2._startMinute < dayEvent1._endMinute)
            || (dayEvent2._endMinute > dayEvent1._startMinute && dayEvent2._endMinute < dayEvent1._endMinute)
            || (dayEvent2._startMinute <= dayEvent1._startMinute && dayEvent2._endMinute >= dayEvent1._endMinute);
        return isOverlap;
    },

    isDayEventOverlapWithGroup: function (dayEvent, dayGroup) {
        if (!dayGroup || !dayGroup.length) return false;
        for (var i = 0; i < dayGroup.length; i++) {
            var theEvent = dayGroup[i];
            if (theEvent === dayEvent._nextEvent) continue;
            var isOverlap = this.isDayEventOverlap(dayEvent, theEvent);
            if (isOverlap) return true;
        }
        return false;
    },

    removeItemFromGroup: function (group, item) {
        for (var i = 0; i < group.length; i++) {
            if (group[i] === item) {
                group.splice(i, 1);
                break;
            }
        }
    },

    findPreEvents: function (event, eventGroups) {
        var preEvents = [];
        if (!event._preEvent) return preEvents;
        var preColumnGroup = null;
        var fromColumn = event._preEvent._column - 1;
        var toColumn = Math.max(fromColumn - 3, 0);
        for (var column = fromColumn; column >= toColumn; column--) {
            preColumnGroup = eventGroups[column];
            if (!preColumnGroup) break;
            for (var i = 0; i < preColumnGroup.length; i++) {
                var theEvent = preColumnGroup[i];
                if (theEvent._nextEvent === event) {
                    preEvents.push(theEvent);
                }
            }
        }
        return preEvents;
    },

    findRightColumnCnt: function (event) {
        var columnCnt = 0;
        var tempEvent = event;
        while (tempEvent._nextEvent) {
            columnCnt++;
            tempEvent = tempEvent._nextEvent;
        }
        return columnCnt;
    },

    log: function (message, params) {
        if (!this.option.isLogEnabled) return;
        if (params) console.log(message, params);
        else console.log(message);

    },

    dumpEvents: function (dayEvents) {
        var eventStrs = [];
        dayEvents.forEach(function (theEvent) {
            eventStrs.push('dayEvents.push(new DayEvent(' + theEvent._startMinute
                + ', ' + theEvent._endMinute
                + ', \'' + theEvent._title + '\'));');
        });
        this.log(eventStrs.join('\r\n'));
    }
};
;
/*
dhtmlxScheduler v.4.3.0 Stardard

This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.

(c) Dinamenta, UAB.
*/
window.dhtmlXScheduler = window.scheduler = { version: "4.3.0" };
if (!window.dhtmlx) {
    dhtmlx = function (obj) {
        for (var a in obj) dhtmlx[a] = obj[a];
        return dhtmlx; //simple singleton
    };
}
dhtmlx.extend_api = function (name, map, ext) {
    var t = window[name];
    if (!t) return; //component not defined
    window[name] = function (obj) {
        var that;

        if (obj && typeof obj == "object" && !obj.tagName) {
            that = t.apply(this, (map._init ? map._init(obj) : arguments));
            //global settings
            for (var a in dhtmlx)
                if (map[a]) this[map[a]](dhtmlx[a]);
            //local settings
            for (var a in obj) {
                if (map[a]) this[map[a]](obj[a]);
                else if (a.indexOf("on") === 0) {
                    this.attachEvent(a, obj[a]);
                }
            }
        } else
            that = t.apply(this, arguments);
        if (map._patch) map._patch(this);
        return that || this;
    };
    window[name].prototype = t.prototype;
    if (ext)
        dhtmlXHeir(window[name].prototype, ext);
};

dhtmlxAjax = {
    get: function (url, callback) {
        var t = new dtmlXMLLoaderObject(true);
        t.async = (arguments.length < 3);
        t.waitCall = callback;
        t.loadXML(url);
        return t;
    },
    post: function (url, post, callback) {
        var t = new dtmlXMLLoaderObject(true);
        t.async = (arguments.length < 4);
        t.waitCall = callback;
        t.loadXML(url, true, post);
        return t;
    },
    getSync: function (url) {
        return this.get(url, null, true);
    },
    postSync: function (url, post) {
        return this.post(url, post, null, true);
    }
};

/**
 *     @desc: Call wrapper
 *     @type: private
 *     @param: funcObject - action handler
 *     @param: dhtmlObject - user data
 *     @returns: function handler
 *     @topic: 0
 */
function callerFunction(funcObject, dhtmlObject) {
    this.handler = function (e) {
        if (!e)
            e = window.event;
        funcObject(e, dhtmlObject);
        return true;
    };
    return this.handler;
}

/**
 *     @desc: Calculate absolute position of html object
 *     @type: private
 *     @param: htmlObject - html object
 *     @topic: 0
 */
function getAbsoluteLeft(htmlObject) {
    return getOffset(htmlObject).left;
}
/**
 *     @desc: Calculate absolute position of html object
 *     @type: private
 *     @param: htmlObject - html object
 *     @topic: 0
 */
function getAbsoluteTop(htmlObject) {
    return getOffset(htmlObject).top;
}

function getOffsetSum(elem) {
    var top = 0, left = 0;
    while (elem) {
        top = top + parseInt(elem.offsetTop);
        left = left + parseInt(elem.offsetLeft);
        elem = elem.offsetParent;
    }
    return { top: top, left: left };
}
function getOffsetRect(elem) {
    var box = elem.getBoundingClientRect();
    var body = document.body;
    var docElem = document.documentElement;
    var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
    var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
    var clientTop = docElem.clientTop || body.clientTop || 0;
    var clientLeft = docElem.clientLeft || body.clientLeft || 0;
    var top = box.top + scrollTop - clientTop;
    var left = box.left + scrollLeft - clientLeft;
    return { top: Math.round(top), left: Math.round(left) };
}
function getOffset(elem) {
    if (elem.getBoundingClientRect) {
        return getOffsetRect(elem);
    } else {
        return getOffsetSum(elem);
    }
}

/**
 *     @desc: Convert string to it boolean representation
 *     @type: private
 *     @param: inputString - string for covertion
 *     @topic: 0
 */
function convertStringToBoolean(inputString) {
    if (typeof (inputString) == "string")
        inputString = inputString.toLowerCase();

    switch (inputString) {
        case "1":
        case "true":
        case "yes":
        case "y":
        case 1:
        case true:
            return true;
        default:
            return false;
    }
}

/**
 *     @desc: find out what symbol to use as url param delimiters in further params
 *     @type: private
 *     @param: str - current url string
 *     @topic: 0
 */
function getUrlSymbol(str) {
    if (str.indexOf("?") != -1)
        return "&";
    else
        return "?";
}

function dhtmlDragAndDropObject() {
    if (window.dhtmlDragAndDrop)
        return window.dhtmlDragAndDrop;

    this.lastLanding = 0;
    this.dragNode = 0;
    this.dragStartNode = 0;
    this.dragStartObject = 0;
    this.tempDOMU = null;
    this.tempDOMM = null;
    this.waitDrag = 0;
    window.dhtmlDragAndDrop = this;

    return this;
}

dhtmlDragAndDropObject.prototype.removeDraggableItem = function (htmlNode) {
    htmlNode.onmousedown = null;
    htmlNode.dragStarter = null;
    htmlNode.dragLanding = null;
};

dhtmlDragAndDropObject.prototype.addDraggableItem = function (htmlNode, dhtmlObject) {
    htmlNode.onmousedown = this.preCreateDragCopy;
    htmlNode.dragStarter = dhtmlObject;
    this.addDragLanding(htmlNode, dhtmlObject);
};

dhtmlDragAndDropObject.prototype.addDragLanding = function (htmlNode, dhtmlObject) {
    htmlNode.dragLanding = dhtmlObject;
};

dhtmlDragAndDropObject.prototype.preCreateDragCopy = function (e) {
    if ((e || window.event) && (e || event).button == 2)
        return;

    if (window.dhtmlDragAndDrop.waitDrag) {
        window.dhtmlDragAndDrop.waitDrag = 0;
        document.body.onmouseup = window.dhtmlDragAndDrop.tempDOMU;
        document.body.onmousemove = window.dhtmlDragAndDrop.tempDOMM;
        return false;
    }

    if (window.dhtmlDragAndDrop.dragNode)
        window.dhtmlDragAndDrop.stopDrag(e);

    window.dhtmlDragAndDrop.waitDrag = 1;
    window.dhtmlDragAndDrop.tempDOMU = document.body.onmouseup;
    window.dhtmlDragAndDrop.tempDOMM = document.body.onmousemove;
    window.dhtmlDragAndDrop.dragStartNode = this;
    window.dhtmlDragAndDrop.dragStartObject = this.dragStarter;
    document.body.onmouseup = window.dhtmlDragAndDrop.preCreateDragCopy;
    document.body.onmousemove = window.dhtmlDragAndDrop.callDrag;
    window.dhtmlDragAndDrop.downtime = new Date().valueOf();


    if ((e) && (e.preventDefault)) {
        e.preventDefault();
        return false;
    }
    return false;
};

dhtmlDragAndDropObject.prototype.callDrag = function (e) {
    if (!e)
        e = window.event;
    var dragger = window.dhtmlDragAndDrop;
    if ((new Date()).valueOf() - dragger.downtime < 100) return;

    //if ((e.button == 0)&&(_isIE))
    //	return dragger.stopDrag();

    if (!dragger.dragNode) {
        if (dragger.waitDrag) {
            dragger.dragNode = dragger.dragStartObject._createDragNode(dragger.dragStartNode, e);

            if (!dragger.dragNode)
                return dragger.stopDrag();

            dragger.dragNode.onselectstart = function () { return false; };
            dragger.gldragNode = dragger.dragNode;
            document.body.appendChild(dragger.dragNode);
            document.body.onmouseup = dragger.stopDrag;
            dragger.waitDrag = 0;
            dragger.dragNode.pWindow = window;
            dragger.initFrameRoute();
        }
        else return dragger.stopDrag(e, true);
    }

    if (dragger.dragNode.parentNode != window.document.body && dragger.gldragNode) {
        var grd = dragger.gldragNode;

        if (dragger.gldragNode.old)
            grd = dragger.gldragNode.old;

        //if (!document.all) dragger.calculateFramePosition();
        grd.parentNode.removeChild(grd);
        var oldBody = dragger.dragNode.pWindow;

        if (grd.pWindow && grd.pWindow.dhtmlDragAndDrop.lastLanding)
            grd.pWindow.dhtmlDragAndDrop.lastLanding.dragLanding._dragOut(grd.pWindow.dhtmlDragAndDrop.lastLanding);

        //		var oldp=dragger.dragNode.parentObject;
        if (_isIE) {
            var div = document.createElement("Div");
            div.innerHTML = dragger.dragNode.outerHTML;
            dragger.dragNode = div.childNodes[0];
        } else
            dragger.dragNode = dragger.dragNode.cloneNode(true);

        dragger.dragNode.pWindow = window;
        //		dragger.dragNode.parentObject=oldp;

        dragger.gldragNode.old = dragger.dragNode;
        document.body.appendChild(dragger.dragNode);
        oldBody.dhtmlDragAndDrop.dragNode = dragger.dragNode;
    }

    dragger.dragNode.style.left = e.clientX + 15 +
        (dragger.fx ? dragger.fx * (-1) : 0) +
        (document.body.scrollLeft || document.documentElement.scrollLeft) + "px";
    dragger.dragNode.style.top = e.clientY + 3 +
        (dragger.fy ? dragger.fy * (-1) : 0) +
        (document.body.scrollTop || document.documentElement.scrollTop) + "px";

    var z;
    if (!e.srcElement)
        z = e.target;
    else
        z = e.srcElement;
    dragger.checkLanding(z, e);
};

dhtmlDragAndDropObject.prototype.calculateFramePosition = function (n) {
    //this.fx = 0, this.fy = 0;
    if (window.name) {
        var el = parent.frames[window.name].frameElement.offsetParent;
        var fx = 0;
        var fy = 0;

        while (el) {
            fx += el.offsetLeft;
            fy += el.offsetTop;
            el = el.offsetParent;
        }

        if ((parent.dhtmlDragAndDrop)) {
            var ls = parent.dhtmlDragAndDrop.calculateFramePosition(1);
            fx += ls.split('_')[0] * 1;
            fy += ls.split('_')[1] * 1;
        }

        if (n)
            return fx + "_" + fy;
        else
            this.fx = fx;
        this.fy = fy;
    }
    return "0_0";
};

dhtmlDragAndDropObject.prototype.checkLanding = function (htmlObject, e) {
    if ((htmlObject) && (htmlObject.dragLanding)) {
        if (this.lastLanding)
            this.lastLanding.dragLanding._dragOut(this.lastLanding);
        this.lastLanding = htmlObject;
        this.lastLanding = this.lastLanding.dragLanding._dragIn(this.lastLanding, this.dragStartNode, e.clientX,
            e.clientY, e);
        this.lastLanding_scr = (_isIE ? e.srcElement : e.target);
    } else {
        if ((htmlObject) && (htmlObject.tagName != "BODY"))
            this.checkLanding(htmlObject.parentNode, e);
        else {
            if (this.lastLanding)
                this.lastLanding.dragLanding._dragOut(this.lastLanding, e.clientX, e.clientY, e);
            this.lastLanding = 0;

            if (this._onNotFound)
                this._onNotFound();
        }
    }
};

dhtmlDragAndDropObject.prototype.stopDrag = function (e, mode) {
    var dragger = window.dhtmlDragAndDrop;

    if (!mode) {
        dragger.stopFrameRoute();
        var temp = dragger.lastLanding;
        dragger.lastLanding = null;

        if (temp)
            temp.dragLanding._drag(dragger.dragStartNode, dragger.dragStartObject, temp,
                (_isIE ? event.srcElement : e.target));
    }
    dragger.lastLanding = null;

    if ((dragger.dragNode) && (dragger.dragNode.parentNode == document.body))
        dragger.dragNode.parentNode.removeChild(dragger.dragNode);
    dragger.dragNode = 0;
    dragger.gldragNode = 0;
    dragger.fx = 0;
    dragger.fy = 0;
    dragger.dragStartNode = 0;
    dragger.dragStartObject = 0;
    document.body.onmouseup = dragger.tempDOMU;
    document.body.onmousemove = dragger.tempDOMM;
    dragger.tempDOMU = null;
    dragger.tempDOMM = null;
    dragger.waitDrag = 0;
};

dhtmlDragAndDropObject.prototype.stopFrameRoute = function (win) {
    if (win)
        window.dhtmlDragAndDrop.stopDrag(1, 1);

    for (var i = 0; i < window.frames.length; i++) {
        try {
            if ((window.frames[i] != win) && (window.frames[i].dhtmlDragAndDrop))
                window.frames[i].dhtmlDragAndDrop.stopFrameRoute(window);
        } catch (e) { }
    }

    try {
        if ((parent.dhtmlDragAndDrop) && (parent != window) && (parent != win))
            parent.dhtmlDragAndDrop.stopFrameRoute(window);
    } catch (e) { }
};

dhtmlDragAndDropObject.prototype.initFrameRoute = function (win, mode) {
    if (win) {
        window.dhtmlDragAndDrop.preCreateDragCopy();
        window.dhtmlDragAndDrop.dragStartNode = win.dhtmlDragAndDrop.dragStartNode;
        window.dhtmlDragAndDrop.dragStartObject = win.dhtmlDragAndDrop.dragStartObject;
        window.dhtmlDragAndDrop.dragNode = win.dhtmlDragAndDrop.dragNode;
        window.dhtmlDragAndDrop.gldragNode = win.dhtmlDragAndDrop.dragNode;
        window.document.body.onmouseup = window.dhtmlDragAndDrop.stopDrag;
        window.waitDrag = 0;

        if (((!_isIE) && (mode)) && ((!_isFF) || (_FFrv < 1.8)))
            window.dhtmlDragAndDrop.calculateFramePosition();
    }
    try {
        if ((parent.dhtmlDragAndDrop) && (parent != window) && (parent != win))
            parent.dhtmlDragAndDrop.initFrameRoute(window);
    } catch (e) { }

    for (var i = 0; i < window.frames.length; i++) {
        try {
            if ((window.frames[i] != win) && (window.frames[i].dhtmlDragAndDrop))
                window.frames[i].dhtmlDragAndDrop.initFrameRoute(window, ((!win || mode) ? 1 : 0));
        } catch (e) { }
    }
};

_isFF = false;
_isIE = false;
_isOpera = false;
_isKHTML = false;
_isMacOS = false;
_isChrome = false;
_FFrv = false;
_KHTMLrv = false;
_OperaRv = false;

if (navigator.userAgent.indexOf('Macintosh') != -1)
    _isMacOS = true;


if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1)
    _isChrome = true;

if ((navigator.userAgent.indexOf('Safari') != -1) || (navigator.userAgent.indexOf('Konqueror') != -1)) {
    _KHTMLrv = parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Safari') + 7, 5));

    if (_KHTMLrv > 525) { //mimic FF behavior for Safari 3.1+
        _isFF = true;
        _FFrv = 1.9;
    } else
        _isKHTML = true;
} else if (navigator.userAgent.indexOf('Opera') != -1) {
    _isOpera = true;
    _OperaRv = parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Opera') + 6, 3));
}


else if (navigator.appName.indexOf("Microsoft") != -1) {
    _isIE = true;
    if ((navigator.appVersion.indexOf("MSIE 8.0") != -1 || navigator.appVersion.indexOf("MSIE 9.0") != -1 || navigator.appVersion.indexOf("MSIE 10.0") != -1) && document.compatMode != "BackCompat") {
        _isIE = 8;
    }
} else if (navigator.appName == 'Netscape' && navigator.userAgent.indexOf("Trident") != -1) {
    //ie11
    _isIE = 8;
} else {
    _isFF = true;
    _FFrv = parseFloat(navigator.userAgent.split("rv:")[1]);
}

function _dhtmlxError(type, name, params) {
    if (!this.catches)
        this.catches = [];

    return this;
}

_dhtmlxError.prototype.catchError = function (type, func_name) {
    this.catches[type] = func_name;
};

_dhtmlxError.prototype.throwError = function (type, name, params) {
    if (this.catches[type])
        return this.catches[type](type, name, params);

    if (this.catches["ALL"])
        return this.catches["ALL"](type, name, params);

    window.alert("Error type: " + arguments[0] + "\nDescription: " + arguments[1]);
    return null;
};

window.dhtmlxError = new _dhtmlxError();

function dhtmlXHeir(a, b) {
    for (var c in b)
        if (typeof (b[c]) == "function")
            a[c] = b[c];
    return a;
}

if (typeof (window.dhtmlxEvent) == 'undefined') {
    window.dhtmlxEvent = function dhtmlxEvent(el, event, handler) {
        if (el.addEventListener)
            el.addEventListener(event, handler, false);

        else if (el.attachEvent)
            el.attachEvent("on" + event, handler);
    };
}



/**
 *   @desc:
 *   @type: private
 */
dhtmlxEventable = function (obj) {
    obj.attachEvent = function (name, catcher, callObj) {
        name = 'ev_' + name.toLowerCase();
        if (!this[name])
            this[name] = new this.eventCatcher(callObj || this);

        return (name + ':' + this[name].addEvent(catcher)); //return ID (event name & event ID)
    };
    obj.callEvent = function (name, arg0) {
        name = 'ev_' + name.toLowerCase();
        if (this[name])
            return this[name].apply(this, arg0);
        return true;
    };
    obj.checkEvent = function (name) {
        return (!!this['ev_' + name.toLowerCase()]);
    };
    obj.eventCatcher = function (obj) {
        var dhx_catch = [];
        var z = function () {
            var res = true;
            for (var i = 0; i < dhx_catch.length; i++) {
                if (dhx_catch[i]) {
                    var zr = dhx_catch[i].apply(obj, arguments);
                    res = res && zr;
                }
            }
            return res;
        };
        z.addEvent = function (ev) {
            if (typeof (ev) != "function")
                ev = eval(ev);
            if (ev)
                return dhx_catch.push(ev) - 1;
            return false;
        };
        z.removeEvent = function (id) {
            dhx_catch[id] = null;
        };
        return z;
    };
    obj.detachEvent = function (id) {
        if (id) {
            var list = id.split(':');           //get EventName and ID
            this[list[0]].removeEvent(list[1]); //remove event
        }
    };
    obj.detachAllEvents = function () {
        for (var name in this) {
            if (name.indexOf("ev_") === 0) {
                this.detachEvent(name);
                this[name] = null;
            }
        }
    };
    obj = null;
};
if (!window.dhtmlx)
    window.dhtmlx = {};

(function () {
    var _dhx_msg_cfg = null;
    function callback(config, result) {
        var usercall = config.callback;
        modality(false);
        config.box.parentNode.removeChild(config.box);
        _dhx_msg_cfg = config.box = null;
        if (usercall)
            usercall(result);
    }
    function modal_key(e) {
        if (_dhx_msg_cfg) {
            e = e || event;
            var code = e.which || event.keyCode;
            if (dhtmlx.message.keyboard) {
                if (code == 13 || code == 32)
                    callback(_dhx_msg_cfg, true);
                if (code == 27)
                    callback(_dhx_msg_cfg, false);
            }
            if (e.preventDefault)
                e.preventDefault();
            return !(e.cancelBubble = true);
        }
    }
    if (document.attachEvent)
        document.attachEvent("onkeydown", modal_key);
    else
        document.addEventListener("keydown", modal_key, true);

    function modality(mode) {
        if (!modality.cover) {
            modality.cover = document.createElement("DIV");
            //necessary for IE only
            modality.cover.onkeydown = modal_key;
            modality.cover.className = "dhx_modal_cover";
            document.body.appendChild(modality.cover);
        }
        var height = document.body.scrollHeight;
        modality.cover.style.display = mode ? "inline-block" : "none";
    }

    function button(text, result, css) {
        // css - for locale-independent class name
        var className = css ? css : (text || "");
        var button_css = "dhtmlx_" + (className).toLowerCase().replace(/ /g, "_") + "_button"; // dhtmlx_ok_button, dhtmlx_click_me_button
        return "<div class='dhtmlx_popup_button " + button_css + "' result='" + result + "' ><div>" + text + "</div></div>";
    }

    function info(text) {
        if (!t.area) {
            t.area = document.createElement("DIV");
            t.area.className = "dhtmlx_message_area";
            t.area.style[t.position] = "5px";
            document.body.appendChild(t.area);
        }

        t.hide(text.id);
        var message = document.createElement("DIV");
        message.innerHTML = "<div>" + text.text + "</div>";
        message.className = "dhtmlx-info dhtmlx-" + text.type;
        message.onclick = function () {
            t.hide(text.id);
            text = null;
        };

        if (t.position == "bottom" && t.area.firstChild)
            t.area.insertBefore(message, t.area.firstChild);
        else
            t.area.appendChild(message);

        if (text.expire > 0)
            t.timers[text.id] = window.setTimeout(function () {
                t.hide(text.id);
            }, text.expire);

        t.pull[text.id] = message;
        message = null;

        return text.id;
    }
    function _boxStructure(config, ok, cancel) {
        var box = document.createElement("DIV");
        box.className = " dhtmlx_modal_box dhtmlx-" + config.type;
        box.setAttribute("dhxbox", 1);

        var inner = '';

        if (config.width)
            box.style.width = config.width;
        if (config.height)
            box.style.height = config.height;
        if (config.title)
            inner += '<div class="dhtmlx_popup_title">' + config.title + '</div>';
        inner += '<div class="dhtmlx_popup_text"><span>' + (config.content ? '' : config.text) + '</span></div><div  class="dhtmlx_popup_controls">';
        if (ok) {
            var ok_text = (config.ok || scheduler.locale.labels.message_ok);
            //default value for compatibility with custom locales some people have
            if (ok_text === undefined) ok_text = "OK";
            inner += button(ok_text, true, "ok");
        }
        if (cancel) {
            var cancel_text = (config.cancel || scheduler.locale.labels.message_cancel);
            if (cancel_text === undefined) cancel_text = "Cancel";
            inner += button(cancel_text, false, "cancel");
        }
        if (config.buttons) {
            for (var i = 0; i < config.buttons.length; i++)
                inner += button(config.buttons[i], i);
        }
        inner += '</div>';
        box.innerHTML = inner;

        if (config.content) {
            var node = config.content;
            if (typeof node == "string")
                node = document.getElementById(node);
            if (node.style.display == 'none')
                node.style.display = "";
            box.childNodes[config.title ? 1 : 0].appendChild(node);
        }

        box.onclick = function (e) {
            e = e || event;
            var source = e.target || e.srcElement;
            if (!source.className) source = source.parentNode;
            if (source.className.split(" ")[0] == "dhtmlx_popup_button") {
                var result = source.getAttribute("result");
                result = (result == "true") || (result == "false" ? false : result);
                callback(config, result);
            }
        };
        config.box = box;
        if (ok || cancel)
            _dhx_msg_cfg = config;

        return box;
    }
    function _createBox(config, ok, cancel) {
        var box = config.tagName ? config : _boxStructure(config, ok, cancel);

        if (!config.hidden)
            modality(true);
        document.body.appendChild(box);
        var x = Math.abs(Math.floor(((window.innerWidth || document.documentElement.offsetWidth) - box.offsetWidth) / 2));
        var y = Math.abs(Math.floor(((window.innerHeight || document.documentElement.offsetHeight) - box.offsetHeight) / 2));
        if (config.position == "top")
            box.style.top = "-3px";
        else
            box.style.top = y + 'px';
        box.style.left = x + 'px';
        //necessary for IE only
        box.onkeydown = modal_key;

        box.focus();
        if (config.hidden)
            dhtmlx.modalbox.hide(box);

        return box;
    }

    function alertPopup(config) {
        return _createBox(config, true, false);
    }
    function confirmPopup(config) {
        return _createBox(config, true, true);
    }
    function boxPopup(config) {
        return _createBox(config);
    }
    function box_params(text, type, callback) {
        if (typeof text != "object") {
            if (typeof type == "function") {
                callback = type;
                type = "";
            }
            text = { text: text, type: type, callback: callback };
        }
        return text;
    }
    function params(text, type, expire, id) {
        if (typeof text != "object")
            text = { text: text, type: type, expire: expire, id: id };
        text.id = text.id || t.uid();
        text.expire = text.expire || t.expire;
        return text;
    }
    dhtmlx.alert = function () {
        var text = box_params.apply(this, arguments);
        text.type = text.type || "confirm";
        return alertPopup(text);
    };
    dhtmlx.confirm = function () {
        var text = box_params.apply(this, arguments);
        text.type = text.type || "alert";
        return confirmPopup(text);
    };
    dhtmlx.modalbox = function () {
        var text = box_params.apply(this, arguments);
        text.type = text.type || "alert";
        return boxPopup(text);
    };
    dhtmlx.modalbox.hide = function (node) {
        while (node && node.getAttribute && !node.getAttribute("dhxbox"))
            node = node.parentNode;
        if (node) {
            node.parentNode.removeChild(node);
            modality(false);
        }
    };
    var t = dhtmlx.message = function (text, type, expire, id) {
        text = params.apply(this, arguments);
        text.type = text.type || "info";

        var subtype = text.type.split("-")[0];
        switch (subtype) {
            case "alert":
                return alertPopup(text);
            case "confirm":
                return confirmPopup(text);
            case "modalbox":
                return boxPopup(text);
            default:
                return info(text);
        }
    };

    t.seed = (new Date()).valueOf();
    t.uid = function () { return t.seed++; };
    t.expire = 4000;
    t.keyboard = true;
    t.position = "top";
    t.pull = {};
    t.timers = {};

    t.hideAll = function () {
        for (var key in t.pull)
            t.hide(key);
    };
    t.hide = function (id) {
        var obj = t.pull[id];
        if (obj && obj.parentNode) {
            window.setTimeout(function () {
                obj.parentNode.removeChild(obj);
                obj = null;
            }, 2000);
            obj.className += " hidden";

            if (t.timers[id])
                window.clearTimeout(t.timers[id]);
            delete t.pull[id];
        }
    };
})();

dhtmlxError.catchError("LoadXML", function (a, b, c) {
    var message = c[0].responseText;

    switch (scheduler.config.ajax_error) {
        case "alert":
            window.alert(message);
            break;
        case "console":
            window.console.log(message);
            break;
        default:
            break;
    }
});


dhtmlxEventable(scheduler);

scheduler._detachDomEvent = function (el, event, handler) {
    if (el.removeEventListener) {
        el.removeEventListener(event, handler, false);

    } else if (el.detachEvent) {
        el.detachEvent("on" + event, handler);
    }
};

scheduler._init_once = function () {

    var oldSize = getWindowSize();
    dhtmlxEvent(window, "resize", function () {
        // Added by Mason
        scheduler.backupOffset();

        var newSize = getWindowSize();

        // ie7-8 triggers "resize" when window's elements are resized, it messes container-autoresize extension
        // check if it's actually resized
        if (!equals(oldSize, newSize)) {
            window.clearTimeout(scheduler._resize_timer);
            scheduler._resize_timer = window.setTimeout(function () {
                if (scheduler.callEvent("onSchedulerResize", [])) {
                    scheduler.update_view();
                    scheduler.callEvent("onAfterSchedulerResize", []);
                }
            }, 100);
        }
        oldSize = newSize;

    });
    function getWindowSize() {
        return {
            w: window.innerWidth || document.documentElement.clientWidth,
            h: window.innerHeight || document.documentElement.clientHeight
        };
    }
    function equals(a, b) {
        return a.w == b.w && a.h == b.h;
    }

    scheduler._init_once = function () { };
};
scheduler.init = function (id, date, mode, notUpdateView) {
    date = date || (scheduler._currentDate());
    mode = mode || "week";

    if (this._obj) {
        this.unset_actions();
    }

    this._obj = (typeof id == "string") ? document.getElementById(id) : id;

    //hook for terrace skin
    if (this._skin_init)
        scheduler._skin_init();

    scheduler.date.init();


    this._els = [];
    this._scroll = true;
    this._quirks = (_isIE && document.compatMode == "BackCompat");
    this._quirks7 = (_isIE && navigator.appVersion.indexOf("MSIE 8") == -1);

    this.get_elements();
    this.init_templates();
    this.set_actions();

    this._init_once();
    this._init_touch_events();

    this.set_sizes();
    scheduler.callEvent('onSchedulerReady', []);

    // Added by Mason
    this.refreshConfig();
    this._events = [];

    this.setCurrentView(date, mode, notUpdateView);

    // Added by Mason
    scheduler.restoreOffset();
};

scheduler.refreshConfig = function () {
    var $thisObj = $(this._obj);
    this.isProcedureTheme = $thisObj.hasClass('appt-theme-procedure');
    this.isStateTheme = $thisObj.hasClass('appt-theme-state');
    this.config.first_minute = (this.config.first_hour || 0) * 60;
    this.config.last_minute = (this.config.last_hour || 24) * 60;
};

scheduler.xy = {
    min_event_height: 15,
    scale_width: 45,
    scroll_width: 10,
    scale_height: 30,
    scale_header_height: 30,
    month_scale_height: 20,
    menu_width: 25,
    margin_top: 0,
    margin_left: 0,
    margin_right: 150,
    margin_bottom: 20,
    editor_width: 140,
    month_head_height: 22
};
scheduler.keys = {
    edit_save: 13,
    edit_cancel: 27
};
scheduler.set_sizes = function () {
    var w = this._x = this._obj.clientWidth - this.xy.margin_left;
    var h = this._y = this._obj.clientHeight - this.xy.margin_top;

    // Added by mason
    if (!this.config.isLiteScheduler) {
        h -= this.xy.margin_bottom;
    }
    this._columnHeight = h;

    //not-table mode always has scroll - need to be fixed in future
    var scale_x = this._table_view ? 0 : (this.xy.scale_width + this.xy.scroll_width);
    var scale_s = this._table_view ? -1 : this.xy.scale_width;

    this.set_xy(this._els["dhx_cal_navline"][0], w, this.xy.nav_height, 0, 0);
    //this.set_xy(this._els["dhx_cal_header"][0], w - scale_x, this.xy.scale_height, scale_s, this.xy.nav_height + (this._quirks ? -1 : 1));
    this.set_xy(this._els["dhx_cal_header"][0], w - scale_x - this.xy.margin_right, this.xy.scale_header_height, scale_s, this.xy.nav_height + (this._quirks ? -1 : 1));
    //to support alter-skin, we need a way to alter height directly from css
    var actual_height = this._els["dhx_cal_navline"][0].offsetHeight;
    if (actual_height > 0) this.xy.nav_height = actual_height;

    //var data_y = this.xy.scale_height + this.xy.nav_height + (this._quirks ? -2 : 0);
    var data_y = this.xy.scale_header_height + this.xy.nav_height + (this._quirks ? -2 : 0);

    // Added by mason, for lite scheduler.
    if (this.config.isLiteScheduler) {
        this.set_xy(this._els["dhx_cal_data"][0], w, h, 0, 0);
    } else {
        this.set_xy(this._els["dhx_cal_data"][0], w - this.xy.margin_right, h - (data_y + 2), 0, data_y + 2);
    }

    // Added by Mason, for scheduler panel.
    if (this.xy.margin_right > 0 && this._els['appt-panel']) {
        var apptPanelHeight = h - data_y + this.xy.scale_header_height + 1;
        this.set_xy(this._els['appt-panel'][0], this.xy.margin_right, apptPanelHeight,
            w - this.xy.margin_right, data_y + 1 - this.xy.scale_header_height);
        var apptPanelBodies = $(this._els['appt-panel'][0]).find('.appt-panel-body');
        var appPanelBodyHeight = apptPanelHeight - 35 - 28 * apptPanelBodies.length;
        apptPanelBodies.css('height', appPanelBodyHeight);
    }

};
scheduler.set_xy = function (node, w, h, x, y) {
    node.style.width = Math.max(0, w) + "px";
    node.style.height = Math.max(0, h) + "px";
    if (arguments.length > 3) {
        node.style.left = x + "px";
        node.style.top = y + "px";
    }
};
scheduler.get_elements = function () {
    //get all child elements as named hash
    var els = this._obj.getElementsByTagName("DIV");
    for (var i = 0; i < els.length; i++) {
        var class_name = els[i].className || "";
        var attr_value = els[i].getAttribute("name") || "";
        if (class_name) class_name = class_name.split(" ")[0];
        if (!this._els[class_name]) this._els[class_name] = [];
        this._els[class_name].push(els[i]);

        //check if name need to be changed
        var label = scheduler.locale.labels[attr_value || class_name];
        if (typeof label !== "string" && attr_value && !els[i].innerHTML)
            label = attr_value.split("_")[0];
        if (label)
            els[i].innerHTML = label;
    }
    this.$aresApptContainer = $(this._els.dhx_cal_data[0]);
};

scheduler.unset_actions = function () {
    for (var a in this._els)
        if (this._click[a])
            for (var i = 0; i < this._els[a].length; i++)
                this._els[a][i].onclick = null;
    this._obj.onselectstart = null;
    this._obj.onmousemove = null;
    this._obj.onmousedown = null;
    this._obj.onmouseup = null;
    this._obj.ondblclick = null;
    this._obj.oncontextmenu = null;

    // Added by Mason
    var dataArea = this._els['dhx_cal_data'][0];
    dataArea.ondrop = null;
    dataArea.ondragover = null;
};

scheduler.set_actions = function () {
    for (var a in this._els)
        if (this._click[a])
            for (var i = 0; i < this._els[a].length; i++)
                this._els[a][i].onclick = scheduler._click[a];
    this._obj.onselectstart = function (e) { return false; };
    this._obj.onmousemove = function (e) {
        if (!scheduler._temp_touch_block)
            scheduler._on_mouse_move(e || event);
    };
    this._obj.onmousedown = function (e) {
        if (!scheduler._ignore_next_click)
            scheduler._on_mouse_down(e || event);
    };
    this._obj.onmouseup = function (e) {
        if (!scheduler._ignore_next_click)
            scheduler._on_mouse_up(e || event);
    };
    this._obj.ondblclick = function (e) {
        scheduler._on_dbl_click(e || event);
    };
    this._obj.oncontextmenu = function (e) {
        var ev = e || event;
        var src = ev.target || ev.srcElement;
        var returnValue = scheduler.callEvent("onContextMenu", [scheduler._locate_event(src), ev]);
        return returnValue;
    };

    // Added by Mason
    var dataArea = scheduler._els['dhx_cal_data'][0];
    dataArea.ondragover = function (ev) {
        ev.preventDefault();
        scheduler.showDragIndicator(ev);
    };
    dataArea.ondrop = function (ev) {
        scheduler.dropAppt(ev);
        scheduler.hideDragIndicator();
    };

};
scheduler.select = function (id) {
    if (this._select_id == id) return;
    scheduler._close_not_saved();
    this.editStop(false);
    this.unselect();
    this._select_id = id;
    this.updateEvent(id);
};
scheduler.unselect = function (id) {
    if (id && id != this._select_id) return;
    var t = this._select_id;
    this._select_id = null;
    if (t && this.getEvent(t)) this.updateEvent(t);
};
scheduler.getState = function () {
    return {
        mode: this._mode,
        date: new Date(this._date),
        min_date: new Date(this._min_date),
        max_date: new Date(this._max_date),
        editor_id: this._edit_id,
        lightbox_id: this._lightbox_id,
        new_event: this._new_event,
        select_id: this._select_id,
        expanded: this.expanded,
        drag_id: this._drag_id,
        drag_mode: this._drag_mode
    };
};
scheduler._click = {
    dhx_cal_data: function (e) {
        //in case of touch disable click processing
        if (scheduler._ignore_next_click) {
            if (e.preventDefault)
                e.preventDefault();
            e.cancelBubble = true;
            scheduler._ignore_next_click = false;
            return false;
        }

        var trg = e ? e.target : event.srcElement;
        var id = scheduler._locate_event(trg);

        e = e || event;

        if (!id) {
            scheduler.callEvent("onEmptyClick", [scheduler.getActionData(e).date, e]);
        } else {
            if (!scheduler.callEvent("onClick", [id, e]) || scheduler.config.readonly) return;
        }

        // Added by Mason
        if (id && !scheduler.config.isLiteScheduler) {
            var $trg = $(trg);
            var apptTileElement = $trg.parents('.appt');
            if (apptTileElement.length < 1) {
                apptTileElement = $trg;
            }
            var event = scheduler.getEvent(id);
            scheduler.callEvent("onApptClicked", [event, apptTileElement]);
        }

        if (id && scheduler.config.select) {
            scheduler.select(id);
            var mask = trg.className;
            if (mask.indexOf("_icon") != -1)
                scheduler._click.buttons[mask.split(" ")[1].replace("icon_", "")](id);
        } else {
            scheduler._close_not_saved();
            if (new Date().valueOf() - (scheduler._new_event || 0) > 500) {
                scheduler.unselect();
            }
        }
    },
    dhx_cal_prev_button: function () {
        scheduler._click.dhx_cal_next_button(0, -1);
    },
    dhx_cal_next_button: function (dummy, step) {
        scheduler.setCurrentView(scheduler.date.add( //next line changes scheduler._date , but seems it has not side-effects
            scheduler.date[scheduler._mode + "_start"](scheduler._date), (step || 1), scheduler._mode));
    },
    dhx_cal_today_button: function () {
        if (scheduler.callEvent("onBeforeTodayDisplayed", [])) {
            scheduler.setCurrentView(scheduler._currentDate());
        }
    },
    dhx_cal_tab: function () {
        var name = this.getAttribute("name");
        var mode = name.substring(0, name.search("_tab"));
        scheduler.setCurrentView(scheduler._date, mode);
    },
    buttons: {
        "delete": function (id) {
            var c = scheduler.locale.labels.confirm_deleting;
            scheduler._dhtmlx_confirm(c, scheduler.locale.labels.title_confirm_deleting, function () { scheduler.deleteEvent(id); });
        },
        edit: function (id) { scheduler.edit(id); },
        save: function (id) { scheduler.editStop(true); },
        details: function (id) { scheduler.showLightbox(id); },
        cancel: function (id) { scheduler.editStop(false); }
    }
};
scheduler._dhtmlx_confirm = function (message, title, callback) {
    if (!message)
        return callback();
    var opts = { text: message };
    if (title)
        opts.title = title;
    if (callback) {
        opts.callback = function (result) {
            if (result)
                callback();
        };
    }
    dhtmlx.confirm(opts);
};
scheduler.addEventNow = function (start, end, e) {
    var base = {};
    if (start && start.constructor.toString().match(/object/i) !== null) {
        base = start;
        start = null;
    }

    var d = (this.config.event_duration || this.config.time_step) * 60000;
    if (!start) start = base.start_date || Math.round((scheduler._currentDate()).valueOf() / d) * d;
    var start_date = new Date(start);
    if (!end) {
        var start_hour = this.config.first_hour;
        if (start_hour > start_date.getHours()) {
            start_date.setHours(start_hour);
            start = start_date.valueOf();
        }
        end = start.valueOf() + d;
    }
    var end_date = new Date(end);

    // scheduler.addEventNow(new Date(), new Date()) + collision though get_visible events defect (such event was not retrieved)
    if (start_date.valueOf() == end_date.valueOf())
        end_date.setTime(end_date.valueOf() + d);

    base.start_date = base.start_date || start_date;
    base.end_date = base.end_date || end_date;
    base.text = base.text || this.locale.labels.new_event;
    base.id = this._drag_id = this.uid();
    this._drag_mode = "new-size";

    this._loading = true;
    this.addEvent(base);
    this.callEvent("onEventCreated", [this._drag_id, e]);
    this._loading = false;

    this._drag_event = {}; //dummy , to trigger correct event updating logic
    this._on_mouse_up(e);
};
scheduler._on_dbl_click = function (e, src) {
    src = src || (e.target || e.srcElement);
    if (this.config.readonly) return;
    var name = (src.className || "").split(" ")[0];
    switch (name) {
        case "dhx_scale_holder":
        case "dhx_scale_holder_now":
        case "dhx_month_body":
        case "dhx_wa_day_data":
            if (!scheduler.config.dblclick_create) break;
            this.addEventNow(this.getActionData(e).date, null, e);
            break;
        case "dhx_cal_event":
        case "dhx_wa_ev_body":
        case "dhx_agenda_line":
        case "dhx_grid_event":
        case "dhx_cal_event_line":
        case "dhx_cal_event_clear":
            var id = this._locate_event(src);
            if (!this.callEvent("onDblClick", [id, e])) return;
            if (this.config.details_on_dblclick || this._table_view || !this.getEvent(id)._timed || !this.config.select)
                this.showLightbox(id);
            else
                //this.edit(id);
                break;
        case "dhx_time_block":
        case "dhx_cal_container":
            return;
        default:
            var t = this["dblclick_" + name];
            if (t) {
                t.call(this, e);
            }
            else {
                if (src.parentNode && src != this)
                    return scheduler._on_dbl_click(e, src.parentNode);
            }
            break;
    }
};
//column index by mouse x-coordinate
scheduler._get_column_index = function (x_pos) {
    var column = 0;
    if (this._cols) {

        var width = 0;
        for (var i = 0; i < this._cols.length && !width; i++) {
            width = this._cols[i];
        }

        if (width) {
            column = x_pos / width;
        } else {
            column = 0;
        }

        if (this._ignores)
            for (var i = 0; i <= column; i++)
                if (this._ignores[i])
                    column++;
    }
    return column;
};
//transform mouse coordinates to day-time indexes of week based view
scheduler._week_indexes_from_pos = function (pos, relative) {
    //"get position" can be invoked before columns are loaded into the units view(e.g. by onMouseMove handler in key_nav.js)
    if (!this._cols) {
        return pos;
    } else {
        var column = this._get_column_index(pos.x);
        pos.x = Math.min(this._cols.length - 1, Math.max(0, Math.ceil(column) - 1));

        pos.y = Math.max(0, Math.ceil(pos.y * 60 / (this.config.time_step * this.config.hour_size_px)) - 1);
        if (!relative) {
            pos.y += this.config.first_hour * (60 / this.config.time_step);
        }
        return pos;
    }
};

scheduler._mouse_coords = function (ev, pos) {
    //var pos;
    if (!pos) {
        var b = document.body;
        var d = document.documentElement;
        if (!_isIE && (ev.pageX || ev.pageY))
            pos = { x: ev.pageX, y: ev.pageY };
        else pos = {
            x: ev.clientX + (b.scrollLeft || d.scrollLeft || 0) - b.clientLeft,
            y: ev.clientY + (b.scrollTop || d.scrollTop || 0) - b.clientTop
        };
    }

    //apply layout
    pos.x -= getAbsoluteLeft(this._obj) + (this._table_view ? 0 : this.xy.scale_width);

    // Added by Mason, for lite scheduler, no navigation bar, should exclude this.xy.nav_height and this.xy.scale_header_height
    if (this.config.isLiteScheduler) {
        pos.y -= getAbsoluteTop(this._obj) + (this._dy_shift || 0) - this._els["dhx_cal_data"][0].scrollTop;
    } else {
        pos.y -= getAbsoluteTop(this._obj) + this.xy.nav_height + (this._dy_shift || 0) + this.xy.scale_header_height - this._els["dhx_cal_data"][0].scrollTop;
    }

    pos.ev = ev;

    var handler = this["mouse_" + this._mode];
    if (handler)
        return handler.call(this, pos);


    //transform to date
    if (!this._table_view) {
        pos = this._week_indexes_from_pos(pos);
    } else {
        var column = this._get_column_index(pos.x);
        if (!this._cols || !this._colsS) // agenda/map views
            return pos;
        var dy = 0;
        for (dy = 1; dy < this._colsS.heights.length; dy++)
            if (this._colsS.heights[dy] > pos.y) break;

        pos.y = Math.ceil((Math.max(0, column) + Math.max(0, dy - 1) * 7) * 24 * 60 / this.config.time_step);

        if (scheduler._drag_mode || this._mode == "month")
            pos.y = (Math.max(0, Math.ceil(column) - 1) + Math.max(0, dy - 1) * 7) * 24 * 60 / this.config.time_step;

        //we care about ignored days only during event moving in month view
        if (this._drag_mode == "move") {
            if (scheduler._ignores_detected && scheduler.config.preserve_length) {
                pos._ignores = true;
                //get real lengtn of event
                if (!this._drag_event._event_length)
                    this._drag_event._event_length = this._get_real_event_length(this._drag_event.start_date, this._drag_event.end_date, { x_step: 1, x_unit: "day" });
            }
        }

        pos.x = 0;
    }
    return pos;
};
scheduler._close_not_saved = function () {
    if (new Date().valueOf() - (scheduler._new_event || 0) > 500 && scheduler._edit_id) {
        var c = scheduler.locale.labels.confirm_closing;

        scheduler._dhtmlx_confirm(c, scheduler.locale.labels.title_confirm_closing, function () { scheduler.editStop(scheduler.config.positive_closing); });
    }
};
scheduler._correct_shift = function (start, back) {
    return start -= ((new Date(scheduler._min_date)).getTimezoneOffset() - (new Date(start)).getTimezoneOffset()) * 60000 * (back ? -1 : 1);
};

scheduler._is_pos_changed = function (old_pos, new_pos) {
    function diff(old_val, new_val, acc) {
        return !!(Math.abs(old_val - new_val) > acc);
    }

    var delay = 100,
        d_pos = 5;

    // start drag only if passed some time since mouse down, or if mouse position changed sufficiently
    return !!(!this._drag_pos.start || (+new Date() - this._drag_pos.start > delay) || diff(old_pos.x, new_pos.x, d_pos) || diff(old_pos.y, new_pos.y, d_pos));
};

scheduler._on_mouse_move = function (e) {
    if (this._drag_mode) {
        var pos = this._mouse_coords(e);
        if (pos.force_redraw || this._is_pos_changed(this._drag_pos, pos)) {
            var start, end;
            if (this._edit_id != this._drag_id)
                this._close_not_saved();

            this._drag_pos = pos;
            this._drag_pos.has_moved = true;

            if (this._drag_mode == "create") {
                this._close_not_saved();
                this.unselect(this._select_id);
                this._loading = true; //will be ignored by dataprocessor

                start = this._get_date_from_pos(pos).valueOf();

                if (!this._drag_start) {
                    var res = this.callEvent("onBeforeEventCreated", [e, this._drag_id]);
                    if (!res)
                        return;


                    this._drag_start = start;
                    return;
                }

                end = start;
                if (end == this._drag_start) {
                }

                var start_date = new Date(this._drag_start);
                var end_date = new Date(end);
                if ((this._mode == "day" || this._mode == "week") &&
                    (start_date.getHours() == end_date.getHours() &&
                        start_date.getMinutes() == end_date.getMinutes())) {
                    end_date = new Date(this._drag_start + 1000);
                }


                this._drag_id = this.uid();
                this.addEvent(start_date, end_date, this.locale.labels.new_event, this._drag_id, pos.fields);

                this.callEvent("onEventCreated", [this._drag_id, e]);
                this._loading = false;
                this._drag_mode = "new-size";

            }

            var ev = this.getEvent(this._drag_id);
            var obj;

            if (this._drag_mode == "move") {
                start = this._min_date.valueOf() + (pos.y * this.config.time_step + pos.x * 24 * 60 - (scheduler._move_pos_shift || 0)) * 60000;
                if (!pos.custom && this._table_view) start += this.date.time_part(ev.start_date) * 1000;
                start = this._correct_shift(start);

                if (pos._ignores && this.config.preserve_length && this._table_view) {
                    if (this.matrix)
                        obj = this.matrix[this._mode];
                    obj = obj || { x_step: 1, x_unit: "day" };
                    end = start * 1 + this._get_fictional_event_length(start, this._drag_event._event_length, obj);
                } else
                    end = ev.end_date.valueOf() - (ev.start_date.valueOf() - start);
            } else { // resize
                start = ev.start_date.valueOf();
                end = ev.end_date.valueOf();
                if (this._table_view) {
                    var resize_date = this._min_date.valueOf() + pos.y * this.config.time_step * 60000 + (pos.custom ? 0 : 24 * 60 * 60000);
                    if (this._mode == "month") {
                        resize_date = this._correct_shift(resize_date, false);
                        if (this._drag_from_start) {
                            var day = 24 * 60 * 60000;
                            if (resize_date <= scheduler.date.date_part(new Date(end + day - 1)).valueOf()) // to get end time as 23:59:59 and then the day start
                                start = resize_date - day;
                        } else {
                            end = resize_date;
                        }
                    } else {
                        if (pos.resize_from_start)
                            start = resize_date;
                        else
                            end = resize_date;
                    }
                } else {
                    end = this.date.date_part(new Date(ev.end_date.valueOf() - 1)).valueOf() + pos.y * this.config.time_step * 60000;
                    this._els["dhx_cal_data"][0].style.cursor = "s-resize";
                    if (this._mode == "week" || this._mode == "day")
                        end = this._correct_shift(end);
                }
                if (this._drag_mode == "new-size") {
                    if (end <= this._drag_start) {
                        var shift = pos.shift || ((this._table_view && !pos.custom) ? 24 * 60 * 60000 : 0);
                        start = end - (pos.shift ? 0 : shift);
                        end = this._drag_start + (shift || (this.config.time_step * 60000));
                    } else {
                        start = this._drag_start;
                    }
                } else {
                    if (end <= start)
                        end = start + this.config.time_step * 60000;
                }
            }
            var new_end = new Date(end - 1);
            var new_start = new Date(start);
            //deny drag out of visible scheduler scale in timeline view
            if (scheduler.config.limit_drag_out &&
                (+new_start < +scheduler._min_date || +end > +scheduler._max_date)) {
                var duration = end - new_start;
                if (+new_start < +scheduler._min_date) {
                    new_start = new Date(scheduler._min_date);
                    end = new Date(+new_start + duration);
                } else {
                    end = new Date(scheduler._max_date);
                    new_start = new Date(+end - duration);
                }
                var new_end = new Date(end - 1);
            }



            // fix event dates when resized to bottom of the column (day/week views)
            if (!this._table_view &&
                !scheduler.config.all_timed &&
                ((!scheduler._get_section_view() && pos.x != this._get_event_sday({ start_date: new Date(end), end_date: new Date(end) })) || new Date(end).getHours() >= this.config.last_hour)) {
                var duration = end - new_start;
                var day = this._min_date.valueOf() + (pos.x * 24 * 60) * 60000;
                end = scheduler.date.date_part(new Date(day));
                end.setHours(this.config.last_hour);
                new_end = new Date(end - 1);
                if (this._drag_mode == "move") {
                    new_start = new Date(+end - duration);
                }
            }

            //prevent out-of-borders situation for day|week view
            if (this._table_view || (new_end.getDate() == new_start.getDate() && new_end.getHours() < this.config.last_hour) || scheduler._allow_dnd) {
                ev.start_date = new_start;
                ev.end_date = new Date(end);
                if (this.config.update_render) {
                    //fix for repaint after dnd and scroll issue, #231
                    var sx = scheduler._els["dhx_cal_data"][0].scrollTop;
                    this.update_view();
                    scheduler._els["dhx_cal_data"][0].scrollTop = sx;
                } else
                    this.updateEvent(this._drag_id);
            }
            if (this._table_view) {
                this.for_rendered(this._drag_id, function (r) {
                    r.className += " dhx_in_move";
                });
            }

            this.callEvent("onEventDrag", [this._drag_id, this._drag_mode, e]);
        }
    } else {
        if (scheduler.checkEvent("onMouseMove")) {
            var id = this._locate_event(e.target || e.srcElement);
            this.callEvent("onMouseMove", [id, e]);
        }
    }
};
scheduler._on_mouse_down = function (e, src) {
    // on Mac we do not get onmouseup event when clicking right mouse button leaving us in dnd state
    // let's ignore right mouse button then
    if (e.button == 2)
        return;

    if (this.config.readonly || this._drag_mode) return;
    src = src || (e.target || e.srcElement);
    var classname = src.className && src.className.split(" ")[0];

    var trg = e ? e.target : event.srcElement;
    var isPlaceHolder = scheduler._locate_placeholder(trg);
    if (isPlaceHolder == "true") {
        return;
    }

    switch (classname) {
        case "dhx_cal_event_line":
        case "dhx_cal_event_clear":
            if (this._table_view)
                this._drag_mode = "move"; //item in table mode
            break;
        case "dhx_event_move":
        case "dhx_wa_ev_body":
            // Added by Mason Start
            var trg = e ? e.target : event.srcElement;
            var id = scheduler._locate_event(trg);
            if (id && !scheduler.config.isLiteScheduler) {
                var $trg = $(trg);
                var apptTileElement = $trg.parents('.appt');
                if (apptTileElement.length < 1) {
                    apptTileElement = $trg;
                }
                var event = scheduler.getEvent(id);
                scheduler.callEvent("onApptClicked", [event, apptTileElement]);
            }
            // Added by Mason End

            this._drag_mode = "move";
            break;
        case "dhx_event_resize":
            this._drag_mode = "resize";
            if ((src.className || "").indexOf("dhx_event_resize_end") < 0) {
                scheduler._drag_from_start = true;
            } else {
                scheduler._drag_from_start = false;
            }
            break;
        case "dhx_scale_holder":
        case "dhx_scale_holder_now":
        case "dhx_month_body":
        case "dhx_matrix_cell":
        case "dhx_marked_timespan":
            this._drag_mode = "create";
            break;
        case "":
            if (src.parentNode)
                return scheduler._on_mouse_down(e, src.parentNode);
            break;
        default:
            if (!scheduler.checkEvent("onMouseDown") || scheduler.callEvent("onMouseDown", [classname])) {
                if (src.parentNode && src != this && classname != "dhx_body") {
                    return scheduler._on_mouse_down(e, src.parentNode);
                }
            }
            this._drag_mode = null;
            this._drag_id = null;
            break;
    }
    if (this._drag_mode) {
        var id = this._locate_event(src);
        if (!this.config["drag_" + this._drag_mode] || !this.callEvent("onBeforeDrag", [id, this._drag_mode, e]))
            this._drag_mode = this._drag_id = 0;
        else {
            this._drag_id = id;
            this._drag_event = scheduler._lame_clone(this.getEvent(this._drag_id) || {});
            this._drag_pos = this._mouse_coords(e);
            this._drag_pos.start = +new Date();
        }
    }
    this._drag_start = null;
};


scheduler._get_private_properties = function (event) {
    var fields = {};
    for (var i in event) {
        if (i.indexOf("_") === 0) {
            fields[i] = true;
        }
    }
    return fields;
};
scheduler._clear_temporary_properties = function (clean, flagged_event) {
    var initial = this._get_private_properties(clean);
    var current_state = this._get_private_properties(flagged_event);
    for (var i in current_state) {
        if (!initial[i]) {
            delete flagged_event[i];
        }
    }
};


scheduler._on_mouse_up = function (e) {
    if (e && e.button == 2 && scheduler.config.touch) return;
    if (this._drag_mode && this._drag_id) {
        this._els["dhx_cal_data"][0].style.cursor = "default";
        //drop

        var drag_id = this._drag_id;
        var mode = this._drag_mode;

        var ev = this.getEvent(this._drag_id);
        if (this._drag_event._dhx_changed || !this._drag_event.start_date || ev.start_date.valueOf() != this._drag_event.start_date.valueOf() || ev.end_date.valueOf() != this._drag_event.end_date.valueOf()) {
            var is_new = (this._drag_mode == "new-size");
            if (!this.callEvent("onBeforeEventChanged", [ev, e, is_new, this._drag_event])) {
                if (is_new)
                    this.deleteEvent(ev.id, true);
                else {
                    this._drag_event._dhx_changed = false;
                    this._clear_temporary_properties(ev, this._drag_event);
                    scheduler._lame_copy(ev, this._drag_event);
                    this.updateEvent(ev.id);
                }
            } else {

                this._drag_id = this._drag_mode = null;
                if (is_new && this.config.edit_on_create) {
                    this.unselect();
                    this._new_event = new Date();//timestamp of creation
                    //if selection disabled - force lightbox usage
                    if (this._table_view || this.config.details_on_create || !this.config.select) {
                        scheduler.callEvent("onDragEnd", [drag_id, mode, e]);
                        return this.showLightbox(drag_id);
                    }
                    this._drag_pos = true; //set flag to trigger full redraw
                    this._select_id = this._edit_id = drag_id;
                } else {
                    if (!this._new_event)
                        this.callEvent(is_new ? "onEventAdded" : "onEventChanged", [drag_id, this.getEvent(drag_id)]);
                }
            }
        }
        if (this._drag_pos && (this._drag_pos.has_moved || this._drag_pos === true)) this.render_view_data(); //redraw even if there is no real changes - necessary for correct positioning item after drag
        scheduler.callEvent("onDragEnd", [drag_id, mode, e]);
    }
    this._drag_id = null;
    this._drag_mode = null;
    this._drag_pos = null;
};

scheduler._trigger_dyn_loading = function () {
    if (this._load_mode && this._load()) {
        this._render_wait = true;
        return true;
    } else {
        return false;
    }
};
scheduler.update_view = function () {
    var view = this[this._mode + "_view"];
    if (view) {
        view(true);
    } else {
        this._reset_scale();
    }

    if (this._trigger_dyn_loading()) {
        return true;
    }

    //alert(this._props);
    this.render_view_data();

    // Added by Mason
    scheduler.restoreOffset();
};

scheduler.isViewExists = function (mode) {
    return !!(scheduler[mode + "_view"] ||
        (scheduler.date[mode + "_start"] && scheduler.templates[mode + "_date"] && scheduler.templates[mode + "_scale_date"]));
};

scheduler.updateView = function (date, mode, notUpdateView) {
    date = date || this._date;
    mode = mode || this._mode;
    var dhx_cal_data = 'dhx_cal_data';

    if (!this._mode)
        this._obj.className += " dhx_scheduler_" + mode; else {
        this._obj.className = this._obj.className.replace("dhx_scheduler_" + this._mode, "dhx_scheduler_" + mode);
    }

    var prev_scroll = (this._mode == mode && this.config.preserve_scroll) ? this._els[dhx_cal_data][0].scrollTop : false; // saving current scroll

    //hide old custom view
    if (this[this._mode + "_view"] && mode && this._mode != mode)
        this[this._mode + "_view"](false);

    this._close_not_saved();

    var dhx_multi_day = 'dhx_multi_day';
    if (this._els[dhx_multi_day]) {
        this._els[dhx_multi_day][0].parentNode.removeChild(this._els[dhx_multi_day][0]);
        this._els[dhx_multi_day] = null;
    }

    this._mode = mode;
    this._date = date;
    this._table_view = (this._mode == "month");

    this._dy_shift = 0;//correction for multiday section in week/day views

    var tabs = this._els["dhx_cal_tab"];
    if (tabs) {//calendar can work without view tabs
        for (var i = 0; i < tabs.length; i++) {
            var name = tabs[i].className;
            name = name.replace(/ active/g, "");
            if (tabs[i].getAttribute("name") == this._mode + "_tab")
                name = name + " active";
            tabs[i].className = name;
        }
    }
    if (!notUpdateView) {
        //show new view
        this.update_view();
    }

    if (typeof prev_scroll == "number") // if we are updating or working with the same view scrollTop should be saved
        this._els[dhx_cal_data][0].scrollTop = prev_scroll; // restoring original scroll
};
scheduler.setCurrentView = function (date, mode, notUpdateView) {
    if (!this.callEvent("onBeforeViewChange", [this._mode, this._date, mode || this._mode, date || this._date])) return;
    this.updateView(date, mode, notUpdateView);
    this.callEvent("onViewChange", [this._mode, this._date]);
};
scheduler._render_x_header = function (i, left, d, h, offset_top) {
    offset_top = offset_top || 0;
    //header scale	
    var head = document.createElement("DIV");
    head.className = "dhx_scale_bar";

    head.className += ' operatoryHeaderContent';

    if (this.templates[this._mode + "_scalex_class"]) {
        //'_scalex_class' - timeline already have similar template, use the same name
        head.className += ' ' + this.templates[this._mode + "_scalex_class"](d);
    }

    var width = this._cols[i] - 1;

    if (this._mode == "month" && i === 0 && this.config.left_border) {
        head.className += " dhx_scale_bar_border";
        left = left + 1;
    }

    //this.set_xy(head, width, this.xy.scale_height - 2, left, offset_top);//-1 for border
    this.set_xy(head, width, this.xy.scale_header_height - 2, left, offset_top);

    // Modify by Mason -- add link
    var html = '';
    if (this._mode == 'unit') {
        html = this.templates["unit_scale_date2"](d, this._mode);
    } else {
        html = this.templates[this._mode + "_scale_date"](d, this._mode); //TODO - move in separate method
    }
    head.innerHTML = html;
    h.appendChild(head);


};

scheduler._get_columns_num = function (from, to) {
    var count = 7;
    if (!scheduler._table_view) {
        var count_n = scheduler.date["get_" + scheduler._mode + "_end"];
        if (count_n) to = count_n(from);
        count = Math.round((to.valueOf() - from.valueOf()) / (1000 * 60 * 60 * 24));
    }
    return count;
};
scheduler._get_timeunit_start = function () {
    //start date of currently displayed time unit(day, week,...)
    return this.date[this._mode + "_start"](new Date(this._date.valueOf()));
};

scheduler._get_view_end = function () {
    var dd = this._get_timeunit_start();
    var ed = scheduler.date.add(dd, 1, this._mode);
    if (!scheduler._table_view) {
        var count_n = scheduler.date["get_" + scheduler._mode + "_end"];
        if (count_n) ed = count_n(dd);
    }
    return ed;
};
scheduler._calc_scale_sizes = function (width, from, to) {
    //calculates number of displayed columns(days/units/month view cols) and their widths
    var summ = width; //border delta
    var count = this._get_columns_num(from, to);

    this._process_ignores(from, count, "day", 1);
    var realcount = count - this._ignores_detected;

    for (var i = 0; i < count; i++) {
        if (this._ignores[i]) {
            this._cols[i] = 0;
            realcount++;
        } else {
            this._cols[i] = Math.floor(summ / (realcount - i));
        }
        summ -= this._cols[i];
        this._colsS[i] = (this._cols[i - 1] || 0) + (this._colsS[i - 1] || (this._table_view ? 0 : this.xy.scale_width + 2));
    }
    this._colsS['col_length'] = count;

    this._colsS[count] = (this._cols[count - 1] + this._colsS[count - 1]) || 0;
};
scheduler._set_scale_col_size = function (div, width, left) {
    var c = this.config;
    this.set_xy(div, width - 1, c.hour_size_px * (c.last_hour - c.first_hour), left + this.xy.scale_width + 1, 0);//-1 for border
};

scheduler._render_scales = function (header, data_area) {
    //render columns in week/units view, or header in month view
    var sd = new Date(scheduler._min_date),
        ed = new Date(scheduler._max_date),
        today = this.date.date_part(scheduler._currentDate());

    var summ = parseInt(header.style.width, 10); //border delta
    var d = new Date(this._min_date);
    var count = this._get_columns_num(sd, ed);
    this._calc_scale_sizes(summ, sd, ed);
    var left = 0;

    header.innerHTML = "";
    for (var i = 0; i < count; i++) {
        if (!this._ignores[i]) {
            this._render_x_header(i, left, d, header);
        }
        if (!this._table_view) {
            var scales = document.createElement("DIV");
            var cls = "dhx_scale_holder";
            if (d.valueOf() == today.valueOf()) cls = "dhx_scale_holder_now";

            if (this._ignores_detected && this._ignores[i]) {
                cls += " dhx_scale_ignore";
            }

            // Added by Mason Start
            //cls += ' scheduler-timeslot-' + scheduler.config.time_slot_height;
            var bgImageName = 'scheduler-bg-' + scheduler.config.time_slot_unit + '-' + scheduler.config.time_slot_height;
            scales.style.backgroundImage = 'url(/app-resource/images/scheduler-bg/' + bgImageName + '.png)';
            // Added by Mason End

            scales.className = cls + " " + this.templates.week_date_class(d, today);

            this._set_scale_col_size(scales, this._cols[i], left);

            data_area.appendChild(scales);
            this.callEvent("onScaleAdd", [scales, d]);
        }

        left += this._cols[i];
        d = this.date.add(d, 1, "day");
        d = this.date.day_start(d);
    }

    // Added by Mason, handle click event for operatory header.
    $(header).find('.operatoryHeaderContent a').click(function () {
        var key = $(this).data('key');
        scheduler.callEvent("onColumnHeaderClicked", [key]);
        return false;
    });

    // Added by Mason, trigger event when scale rendered finished.
    this.callEvent("onScalesRendered", [data_area, sd, ed]);

};

scheduler._reset_scale = function () {
    //we mustn't call reset_scale for such modes, so it just to be sure
    if (!this.templates[this._mode + "_date"]) return;

    var h = this._els["dhx_cal_header"][0];
    var data_area = this._els["dhx_cal_data"][0];
    var c = this.config;

    h.innerHTML = "";
    //data_area.scrollTop = 0; //fix flickering in FF; makes IE8 flicker instead
    data_area.innerHTML = "";

    var str = ((c.readonly || (!c.drag_resize)) ? " dhx_resize_denied" : "") + ((c.readonly || (!c.drag_move)) ? " dhx_move_denied" : "");
    data_area.className = "dhx_cal_data" + str + " scheduler-bg-null";

    this._scales = {};
    this._cols = [];	//store for data section
    this._colsS = { height: 0 };
    this._dy_shift = 0;

    this.set_sizes();

    // Added by Mason
    if (this._columnHeight > 0) {
        //alert(this._els["dhx_cal_data"][0]);
        scheduler.resetTimeSlotHeight(this._columnHeight);
    }

    var d, sd, today;
    var dd = this._get_timeunit_start(),
        ed = scheduler._get_view_end();

    d = sd = this._table_view ? scheduler.date.week_start(dd) : dd;


    this._min_date = d;
    //this._els["dhx_cal_date"][0].innerHTML = this.templates[this._mode + "_date"](dd, ed, this._mode);
    var titleHtml;
    if (this._mode === 'unit') {
        //$('.dayTitle').html('<span class="scheduler-title">' + moment(dd).format('YYYY-MM-DD dddd') + '</span>');
        titleHtml = this.templates["day_date"](dd, ed, this._mode);
    } else if (this._mode === 'week') {
        titleHtml = this.templates["week_date"](dd, ed, this._mode);
    }
    $('.dayTitle').html(titleHtml);

    this._max_date = ed;
    scheduler._render_scales(h, data_area);

    if (this._table_view) // month view
        this._reset_month_scale(data_area, dd, sd);
    else {
        this._reset_hours_scale(data_area, dd, sd);
        if (c.multi_day) {
            var dhx_multi_day = 'dhx_multi_day';

            if (this._els[dhx_multi_day]) {
                this._els[dhx_multi_day][0].parentNode.removeChild(this._els[dhx_multi_day][0]);
                this._els[dhx_multi_day] = null;
            }

            var navline = this._els["dhx_cal_navline"][0];
            var top = navline.offsetHeight + this._els["dhx_cal_header"][0].offsetHeight + 1;

            var c1 = document.createElement("DIV");
            c1.className = dhx_multi_day;
            c1.style.visibility = "hidden";
            this.set_xy(c1, Math.max(this._colsS[this._colsS.col_length] + this.xy.scroll_width - 2, 0), 0, 0, top); // 2 extra borders, dhx_header has -1 bottom margin
            data_area.parentNode.insertBefore(c1, data_area);

            var c2 = c1.cloneNode(true);
            c2.className = dhx_multi_day + "_icon";
            c2.style.visibility = "hidden";
            this.set_xy(c2, this.xy.scale_width, 0, 0, top); // dhx_header has -1 bottom margin

            c1.appendChild(c2);
            this._els[dhx_multi_day] = [c1, c2];
            this._els[dhx_multi_day][0].onclick = this._click.dhx_cal_data;
        }
    }
};
scheduler._reset_hours_scale = function (b, dd, sd) {
    var c = document.createElement("DIV");
    c.className = "dhx_scale_holder";
    //c.style.backgroundImage = "none";
    var bgImageName = 'scheduler-bg-' + scheduler.config.time_slot_unit + '-' + scheduler.config.time_slot_height;
    c.style.backgroundImage = 'url(/app-resource/images/scheduler-bg/' + bgImageName + '.png)';
    c.innerHTML = this.buildDayLedger();
    b.appendChild(c);
    if (this.config.scroll_hour)
        b.scrollTop = this.config.hour_size_px * (this.config.scroll_hour - this.config.first_hour);
};

// Added
scheduler.buildDayLedger = function () {
    var timeSlotUnit = scheduler.config.time_slot_unit;
    var timeSlotHeight = scheduler.xy.scale_height;
    //var option = { timeSlotUnit: 30, timeSlotHeight: 30 };
    var html = [];
    var timeSlotUnit = scheduler.config.time_slot_unit;
    var timeSlotCnt = 60 / timeSlotUnit;
    var dayLedgerItemHeight = timeSlotCnt * timeSlotHeight;
    var startHour = this.config.first_hour;
    var lastHour = this.config.last_hour;
    var heightStyle = ' style="height:' + dayLedgerItemHeight + 'px"';
    var ledgerItemHtml = '<div class="dayLedgerItem"' + heightStyle + '>';
    var minuteContainerHtml = '<div class="dayLedgerMinuteTextWrapper"' + heightStyle + '>';
    var minuteHtml = '<div class="dayLedgerMinuteText" style="line-height:' + timeSlotHeight + 'px">';
    var hourHtml = '<div class="dayLedgerHourText" style="line-height:' + (dayLedgerItemHeight - 1) + 'px">';
    for (var hour = startHour; hour < lastHour; hour++) {
        html.push(ledgerItemHtml);
        html.push(minuteContainerHtml);
        for (var timeSlotIndex = 1; timeSlotIndex <= timeSlotCnt; timeSlotIndex++) {
            var minute = (timeSlotIndex - 1) * timeSlotUnit;
            var minuteText = this.formatMinuteText(minute);
            html.push(minuteHtml + minuteText + '</div>');
        }
        html.push("</div>");

        var hourText = this.fomatHourText(hour);
        html.push(hourHtml + hourText + '</div>');
        html.push('</div>');
    }
    return html.join('');
};

scheduler.fomatHourText = function (h) {
    return h < 10 ? "0" + h : h;
};

scheduler.formatMinuteText = function (m) {
    return m < 10 ? "0" + m : m;
};

scheduler._currentDate = function () {
    if (scheduler.config.now_date) {
        return new Date(scheduler.config.now_date);
    }
    return new Date();
};

scheduler._process_ignores = function (sd, n, mode, step, preserve) {
    this._ignores = {};
    this._ignores_detected = 0;
    var ignore = scheduler["ignore_" + this._mode];

    if (ignore) {
        var ign_date = new Date(sd);
        for (var i = 0; i < n; i++) {
            if (ignore(ign_date)) {
                this._ignores_detected += 1;
                this._ignores[i] = true;
                if (preserve)
                    n++;
            }
            ign_date = scheduler.date.add(ign_date, step, mode);
            if (scheduler.date[mode + '_start'])
                ign_date = scheduler.date[mode + '_start'](ign_date);
        }
    }
};

scheduler._render_month_scale = function (div, dd/*month start*/, sd/*view start*/) {
    //renders month view layout

    var ed = scheduler.date.add(dd, 1, "month"),
        view_start = new Date(sd);
    var cd = scheduler._currentDate();
    this.date.date_part(cd);
    this.date.date_part(sd);

    var rows = Math.ceil(Math.round((ed.valueOf() - sd.valueOf()) / (60 * 60 * 24 * 1000)) / 7);
    var tdcss = [];

    for (var i = 0; i <= 7; i++) {
        var cell_width = ((this._cols[i] || 0) - 1);
        if (i === 0 && this.config.left_border) {
            cell_width = cell_width - 1;
        }
        tdcss[i] = " style='width:" + cell_width + "px;";
    }

    function getCellHeight(row) {
        var h = scheduler._colsS.height;
        if (scheduler._colsS.heights[row + 1] !== undefined) {
            h = scheduler._colsS.heights[row + 1] - (scheduler._colsS.heights[row] || 0);
        }
        return h;
    }


    var cellheight = 0;

    var html = "<table cellpadding='0' cellspacing='0'>";
    var rendered_dates = [];
    for (var i = 0; i < rows; i++) {
        html += "<tr>";
        var row_height = Math.max(getCellHeight(i) - scheduler.xy.month_head_height, 0);
        for (var j = 0; j < 7; j++) {
            html += "<td";

            var cls = "";
            if (sd < dd)
                cls = 'dhx_before';
            else if (sd >= ed)
                cls = 'dhx_after';
            else if (sd.valueOf() == cd.valueOf())
                cls = 'dhx_now';

            if (this._ignores_detected && this._ignores[j]) {
                cls += " dhx_scale_ignore";
            }

            html += " class='" + cls + " " + this.templates.month_date_class(sd, cd) + "' >";
            var body_class = "dhx_month_body";
            var head_class = "dhx_month_head";
            if (j === 0 && this.config.left_border) {
                body_class += " dhx_month_body_border";
                head_class += " dhx_month_head_border";
            }
            if (!this._ignores_detected || !this._ignores[j]) {
                html += "<div class='" + head_class + "'>" + this.templates.month_day(sd) + "</div>";
                html += "<div class='" + body_class + "' " + tdcss[j] + ";height:" + row_height + "px;'></div></td>";
            } else {
                html += "<div></div><div></div>";
            }
            rendered_dates.push(sd);
            var bf1 = sd.getDate();
            sd = this.date.add(sd, 1, "day");
            if (sd.getDate() - bf1 > 1)
                sd = new Date(sd.getFullYear(), sd.getMonth(), bf1 + 1, 12, 0);
        }
        html += "</tr>";

        scheduler._colsS.heights[i] = cellheight;
        cellheight += getCellHeight(i);
    }
    html += "</table>";

    this._min_date = view_start;
    this._max_date = sd;

    div.innerHTML = html;

    this._scales = {};
    var divs = div.getElementsByTagName('div');
    for (var i = 0; i < rendered_dates.length; i++) { // [header, body, header, body, ...]
        var div = divs[(i * 2) + 1];
        var date = rendered_dates[i];
        this._scales[+date] = div;
    }
    for (var i = 0; i < rendered_dates.length; i++) {
        var date = rendered_dates[i];
        this.callEvent("onScaleAdd", [this._scales[+date], date]);
    }



    return this._max_date;
};

scheduler._reset_month_scale = function (b, dd, sd) {
    //recalculates rows height and redraws month layout
    var ed = scheduler.date.add(dd, 1, "month");

    //trim time part for comparation reasons
    var cd = scheduler._currentDate();
    this.date.date_part(cd);
    this.date.date_part(sd);

    var rows = Math.ceil(Math.round((ed.valueOf() - sd.valueOf()) / (60 * 60 * 24 * 1000)) / 7);

    var height = (Math.floor(b.clientHeight / rows) - this.xy.month_head_height);

    this._colsS.height = height + this.xy.month_head_height;
    this._colsS.heights = [];

    return scheduler._render_month_scale(b, dd, sd);

};
scheduler.getLabel = function (property, key) {
    var sections = this.config.lightbox.sections;
    for (var i = 0; i < sections.length; i++) {
        if (sections[i].map_to == property) {
            var options = sections[i].options;
            for (var j = 0; j < options.length; j++) {
                if (options[j].key == key) {
                    return options[j].label;
                }
            }
        }
    }
    return "";
};
scheduler.updateCollection = function (list_name, collection) {
    var list = scheduler.serverList(list_name);
    if (!list) return false;
    list.splice(0, list.length);
    list.push.apply(list, collection || []);
    scheduler.callEvent("onOptionsLoad", []);
    scheduler.resetLightbox();
    return true;
};
scheduler._lame_clone = function (object, cache) {
    var i, t, result; // iterator, types array, result

    cache = cache || [];

    for (i = 0; i < cache.length; i += 2)
        if (object === cache[i])
            return cache[i + 1];

    if (object && typeof object == "object") {
        result = {};
        t = [Array, Date, Number, String, Boolean];
        for (i = 0; i < t.length; i++) {
            if (object instanceof t[i])
                result = i ? new t[i](object) : new t[i](); // first one is array
        }
        cache.push(object, result);
        for (i in object) {
            if (Object.prototype.hasOwnProperty.apply(object, [i]))
                result[i] = scheduler._lame_clone(object[i], cache);
        }
    }
    return result || object;
};
scheduler._lame_copy = function (target, source) {
    for (var key in source) {
        if (source.hasOwnProperty(key)) {
            target[key] = source[key];
        }
    }
    return target;
};
scheduler._get_date_from_pos = function (pos) {
    var start = this._min_date.valueOf() + (pos.y * this.config.time_step + (this._table_view ? 0 : pos.x) * 24 * 60) * 60000;
    return new Date(this._correct_shift(start));
};
// n_ev - native event
scheduler.getActionData = function (n_ev) {
    var pos = this._mouse_coords(n_ev);
    return {
        date: this._get_date_from_pos(pos),
        section: pos.section
    };
};
scheduler._focus = function (node, select) {
    if (node && node.focus) {
        if (this.config.touch) {
            window.setTimeout(function () {
                node.focus();
            }, 100);
        } else {
            if (select && node.select) node.select();
            node.focus();
        }
    }
};

//non-linear scales
scheduler._get_real_event_length = function (sd, fd, obj) {
    var ev_length = fd - sd;
    var hours = (obj._start_correction + obj._end_correction) || 0;
    var ignore = this["ignore_" + this._mode];

    var start_slot = 0,
        end_slot;
    if (obj.render) {
        start_slot = this._get_date_index(obj, sd);
        end_slot = this._get_date_index(obj, fd);
    } else {
        end_slot = Math.round(ev_length / 60 / 60 / 1000 / 24);
    }

    while (start_slot < end_slot) {
        var check = scheduler.date.add(fd, -obj.x_step, obj.x_unit);
        if (ignore && ignore(fd))
            ev_length -= (fd - check);
        else
            ev_length -= hours;

        fd = check;
        end_slot--;
    }
    return ev_length;
};
scheduler._get_fictional_event_length = function (end_date, ev_length, obj, back) {
    var sd = new Date(end_date);
    var dir = back ? -1 : 1;

    //get difference caused by first|last hour
    if (obj._start_correction || obj._end_correction) {
        var today;
        if (back)
            today = (sd.getHours() * 60 + sd.getMinutes()) - (obj.first_hour || 0) * 60;
        else
            today = (obj.last_hour || 0) * 60 - (sd.getHours() * 60 + sd.getMinutes());
        var per_day = (obj.last_hour - obj.first_hour) * 60;
        var days = Math.ceil((ev_length / (60 * 1000) - today) / per_day);
        ev_length += days * (24 * 60 - per_day) * 60 * 1000;
    }

    var fd = new Date(end_date * 1 + ev_length * dir);
    var ignore = this["ignore_" + this._mode];

    var start_slot = 0,
        end_slot;
    if (obj.render) {
        start_slot = this._get_date_index(obj, sd);
        end_slot = this._get_date_index(obj, fd);
    } else {
        end_slot = Math.round(ev_length / 60 / 60 / 1000 / 24);
    }

    while (start_slot * dir <= end_slot * dir) {
        var check = scheduler.date.add(sd, obj.x_step * dir, obj.x_unit);
        if (ignore && ignore(sd)) {
            ev_length += (check - sd) * dir;
            end_slot += dir;
        }

        sd = check;
        start_slot += dir;
    }

    return ev_length;
};

scheduler._get_section_view = function () {
    if (this.matrix && this.matrix[this._mode]) {
        return this.matrix[this._mode];
    } else if (this._props && this._props[this._mode]) {
        return this._props[this._mode];
    }
    return null;
};

scheduler._get_section_property = function () {
    if (this.matrix && this.matrix[this._mode]) {
        return this.matrix[this._mode].y_property;
    } else if (this._props && this._props[this._mode]) {
        return this._props[this._mode].map_to;
    }
    return null;
};

scheduler._is_initialized = function () {
    var state = this.getState();
    return (this._obj && state.date && state.mode);
};
scheduler._is_lightbox_open = function () {
    var state = this.getState();
    return state.lightbox_id !== null && state.lightbox_id !== undefined;
};
scheduler.date = {
    init: function () {
        var s = scheduler.locale.date.month_short;
        var t = scheduler.locale.date.month_short_hash = {};
        for (var i = 0; i < s.length; i++)
            t[s[i]] = i;

        var s = scheduler.locale.date.month_full;
        var t = scheduler.locale.date.month_full_hash = {};
        for (var i = 0; i < s.length; i++)
            t[s[i]] = i;
    },
    date_part: function (date) {
        var old = new Date(date);
        date.setHours(0);
        date.setMinutes(0);
        date.setSeconds(0);
        date.setMilliseconds(0);
        if (date.getHours() && //shift to yesterday on dst
            (date.getDate() < old.getDate() || date.getMonth() < old.getMonth() || date.getFullYear() < old.getFullYear()))
            date.setTime(date.getTime() + 60 * 60 * 1000 * (24 - date.getHours()));
        return date;
    },
    time_part: function (date) {
        return (date.valueOf() / 1000 - date.getTimezoneOffset() * 60) % 86400;
    },
    week_start: function (date) {
        var shift = date.getDay();
        if (scheduler.config.start_on_monday) {
            if (shift === 0) shift = 6;
            else shift--;
        }
        return this.date_part(this.add(date, -1 * shift, "day"));
    },
    month_start: function (date) {
        date.setDate(1);
        return this.date_part(date);
    },
    year_start: function (date) {
        date.setMonth(0);
        return this.month_start(date);
    },
    day_start: function (date) {
        return this.date_part(date);
    },
    _add_days: function (date, inc) {
        var ndate = new Date(date.valueOf());

        ndate.setDate(ndate.getDate() + inc);
        if ((!date.getHours() && ndate.getHours()) &&//shift to yesterday on dst
            (ndate.getDate() < date.getDate() || ndate.getMonth() < date.getMonth() || ndate.getFullYear() < date.getFullYear()))
            ndate.setTime(ndate.getTime() + 60 * 60 * 1000 * (24 - ndate.getHours()));
        return ndate;
    },
    add: function (date, inc, mode) {
        var ndate = new Date(date.valueOf());
        switch (mode) {
            case "day":
                ndate = scheduler.date._add_days(ndate, inc);
                break;
            case "week":
                ndate = scheduler.date._add_days(ndate, inc * 7);
                break;
            case "month": ndate.setMonth(ndate.getMonth() + inc); break;
            case "year": ndate.setYear(ndate.getFullYear() + inc); break;
            case "hour":
                /*
				 setHour(getHour() + inc) and setMinutes gives weird result when is applied on a Daylight Saving time switch
				 setTime seems working as expected
				*/
                ndate.setTime(ndate.getTime() + inc * 60 * 60 * 1000);
                break;
            case "minute":
                ndate.setTime(ndate.getTime() + inc * 60 * 1000);
                break;
            default:
                return scheduler.date["add_" + mode](date, inc, mode);
        }
        return ndate;
    },
    to_fixed: function (num) {
        if (num < 10) return "0" + num;
        return num;
    },
    copy: function (date) {
        return new Date(date.valueOf());
    },
    date_to_str: function (format, utc) {
        format = format.replace(/%[a-zA-Z]/g, function (a) {
            switch (a) {
                case "%d": return "\"+scheduler.date.to_fixed(date.getDate())+\"";
                case "%m": return "\"+scheduler.date.to_fixed((date.getMonth()+1))+\"";
                case "%j": return "\"+date.getDate()+\"";
                case "%n": return "\"+(date.getMonth()+1)+\"";
                case "%y": return "\"+scheduler.date.to_fixed(date.getFullYear()%100)+\"";
                case "%Y": return "\"+date.getFullYear()+\"";
                case "%D": return "\"+scheduler.locale.date.day_short[date.getDay()]+\"";
                case "%l": return "\"+scheduler.locale.date.day_full[date.getDay()]+\"";
                case "%M": return "\"+scheduler.locale.date.month_short[date.getMonth()]+\"";
                case "%F": return "\"+scheduler.locale.date.month_full[date.getMonth()]+\"";
                case "%h": return "\"+scheduler.date.to_fixed((date.getHours()+11)%12+1)+\"";
                case "%g": return "\"+((date.getHours()+11)%12+1)+\"";
                case "%G": return "\"+date.getHours()+\"";
                case "%H": return "\"+scheduler.date.to_fixed(date.getHours())+\"";
                case "%i": return "\"+scheduler.date.to_fixed(date.getMinutes())+\"";
                case "%a": return "\"+(date.getHours()>11?\"pm\":\"am\")+\"";
                case "%A": return "\"+(date.getHours()>11?\"PM\":\"AM\")+\"";
                case "%s": return "\"+scheduler.date.to_fixed(date.getSeconds())+\"";
                case "%W": return "\"+scheduler.date.to_fixed(scheduler.date.getISOWeek(date))+\"";
                default: return a;
            }
        });
        if (utc) format = format.replace(/date\.get/g, "date.getUTC");
        return new Function("date", "return \"" + format + "\";");
    },
    str_to_date: function (format, utc) {
        var splt = "var temp=date.match(/[a-zA-Z]+|[0-9]+/g);";
        var mask = format.match(/%[a-zA-Z]/g);
        for (var i = 0; i < mask.length; i++) {
            switch (mask[i]) {
                case "%j":
                case "%d": splt += "set[2]=temp[" + i + "]||1;";
                    break;
                case "%n":
                case "%m": splt += "set[1]=(temp[" + i + "]||1)-1;";
                    break;
                case "%y": splt += "set[0]=temp[" + i + "]*1+(temp[" + i + "]>50?1900:2000);";
                    break;
                case "%g":
                case "%G":
                case "%h":
                case "%H":
                    splt += "set[3]=temp[" + i + "]||0;";
                    break;
                case "%i":
                    splt += "set[4]=temp[" + i + "]||0;";
                    break;
                case "%Y": splt += "set[0]=temp[" + i + "]||0;";
                    break;
                case "%a":
                case "%A": splt += "set[3]=set[3]%12+((temp[" + i + "]||'').toLowerCase()=='am'?0:12);";
                    break;
                case "%s": splt += "set[5]=temp[" + i + "]||0;";
                    break;
                case "%M": splt += "set[1]=scheduler.locale.date.month_short_hash[temp[" + i + "]]||0;";
                    break;
                case "%F": splt += "set[1]=scheduler.locale.date.month_full_hash[temp[" + i + "]]||0;";
                    break;
                default:
                    break;
            }
        }
        var code = "set[0],set[1],set[2],set[3],set[4],set[5]";
        if (utc) code = " Date.UTC(" + code + ")";
        return new Function("date", "var set=[0,0,1,0,0,0]; " + splt + " return new Date(" + code + ");");
    },
    getISOWeek: function (ndate) {
        if (!ndate) return false;
        var nday = ndate.getDay();
        if (nday === 0) {
            nday = 7;
        }
        var first_thursday = new Date(ndate.valueOf());
        first_thursday.setDate(ndate.getDate() + (4 - nday));
        var year_number = first_thursday.getFullYear(); // year of the first Thursday
        var ordinal_date = Math.round((first_thursday.getTime() - new Date(year_number, 0, 1).getTime()) / 86400000); //ordinal date of the first Thursday - 1 (so not really ordinal date)
        var week_number = 1 + Math.floor(ordinal_date / 7);
        return week_number;
    },
    getUTCISOWeek: function (ndate) {
        return this.getISOWeek(this.convert_to_utc(ndate));
    },
    convert_to_utc: function (date) {
        return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
    }
};
scheduler.locale = {
    date: {
        month_full: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
        month_short: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
        day_full: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
        day_short: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
    },
    labels: {
        dhx_cal_today_button: "Today",
        day_tab: "Day",
        week_tab: "Week",
        month_tab: "Month",
        new_event: "新建预约",
        icon_save: "Save",
        icon_cancel: "Cancel",
        icon_details: "Details",
        icon_edit: "Edit",
        icon_delete: "Delete",
        confirm_closing: "",//Your changes will be lost, are your sure ?
        confirm_deleting: "Event will be deleted permanently, are you sure?",
        section_description: "Description",
        section_time: "Time period",
        full_day: "Full day",

        /*recurring events*/
        confirm_recurring: "Do you want to edit the whole set of repeated events?",
        section_recurring: "Repeat event",
        button_recurring: "Disabled",
        button_recurring_open: "Enabled",
        button_edit_series: "Edit series",
        button_edit_occurrence: "Edit occurrence",

        /*agenda view extension*/
        agenda_tab: "Agenda",
        date: "Date",
        description: "Description",

        /*year view extension*/
        year_tab: "Year",

        /* week agenda extension */
        week_agenda_tab: "Agenda",

        /*grid view extension*/
        grid_tab: "Grid",

        /* touch tooltip*/
        drag_to_create: "Drag to create",
        drag_to_move: "Drag to move",

        /* dhtmlx message default buttons */
        message_ok: "OK",
        message_cancel: "Cancel"
    }
};


/*
%e	Day of the month without leading zeros (01..31)
%d	Day of the month, 2 digits with leading zeros (01..31)
%j	Day of the year, 3 digits with leading zeros (001..366)
%a	A textual representation of a day, two letters
%W	A full textual representation of the day of the week

%c	Numeric representation of a month, without leading zeros (0..12)
%m	Numeric representation of a month, with leading zeros (00..12)
%b	A short textual representation of a month, three letters (Jan..Dec)
%M	A full textual representation of a month, such as January or March (January..December)

%y	A two digit representation of a year (93..03)
%Y	A full numeric representation of a year, 4 digits (1993..03)
*/

scheduler.config = {
    default_date: "%j %M %Y",
    month_date: "%F %Y",
    load_date: "%Y-%m-%d",
    week_date: "%l",
    day_date: "%D, %F %j",
    hour_date: "%H:%i",
    month_day: "%d",
    xml_date: "%m/%d/%Y %H:%i",
    api_date: "%d-%m-%Y %H:%i",
    preserve_length: true,
    time_step: 15,
    time_slot_unit: 15,
    time_slot_height: 30,
    min_time_slot_height: 30,

    start_on_monday: 1,
    first_hour: 0,
    last_hour: 24,
    readonly: false,
    drag_resize: 1,
    drag_move: 1,
    drag_create: 1,
    dblclick_create: 1,
    edit_on_create: 1,
    details_on_create: 0,
    resize_month_events: false,
    resize_month_timed: false,

    cascade_event_display: false,
    cascade_event_count: 4,
    cascade_event_margin: 30,

    multi_day: false,
    multi_day_height_limit: 0,

    drag_lightbox: true,
    preserve_scroll: true,
    select: true,

    server_utc: false,
    touch: true,
    touch_tip: true,
    touch_drag: 500,
    quick_info_detached: true,

    positive_closing: false,

    drag_highlight: true,
    limit_drag_out: false,
    icons_edit: ["icon_save", "icon_cancel"],
    icons_select: ["icon_details", "icon_edit", "icon_delete"],
    buttons_left: ["dhx_save_btn", "dhx_cancel_btn"],
    buttons_right: ["dhx_delete_btn"],
    lightbox: {
        sections: [
            { name: "description", height: 200, map_to: "text", type: "textarea", focus: true },
            { name: "time", height: 72, type: "time", map_to: "auto" }
        ]
    },
    highlight_displayed_event: true,
    left_border: false,

    ajax_error: "alert",//"ignore"|"console"
    delay_render: 0
};
scheduler.templates = {};
scheduler.init_templates = function () {
    var labels = scheduler.locale.labels;
    labels.dhx_save_btn = labels.icon_save;
    labels.dhx_cancel_btn = labels.icon_cancel;
    labels.dhx_delete_btn = labels.icon_delete;


    var d = scheduler.date.date_to_str;
    var c = scheduler.config;
    var f = function (a, b) {
        for (var c in b)
            if (!a[c]) a[c] = b[c];
    };
    f(scheduler.templates, {
        day_date: d(c.default_date),
        month_date: d(c.month_date),
        week_date: function (d1, d2) {
            return scheduler.templates.day_date(d1) + " &ndash; " + scheduler.templates.day_date(scheduler.date.add(d2, -1, "day"));
        },
        day_scale_date: d(c.default_date),
        month_scale_date: d(c.week_date),
        week_scale_date: d(c.day_date),
        hour_scale: d(c.hour_date),
        time_picker: d(c.hour_date),
        event_date: d(c.hour_date),
        month_day: d(c.month_day),
        xml_date: scheduler.date.str_to_date(c.xml_date, c.server_utc),
        load_format: d(c.load_date, c.server_utc),
        xml_format: d(c.xml_date, c.server_utc),
        api_date: scheduler.date.str_to_date(c.api_date),
        event_header: function (start, end, ev) {
            return scheduler.templates.event_date(start) + " - " + scheduler.templates.event_date(end);
        },
        event_text: function (start, end, ev) {
            return ev.text;
        },
        event_class: function (start, end, ev) {
            return "";
        },
        month_date_class: function (d) {
            return "";
        },
        week_date_class: function (d) {
            return "";
        },
        event_bar_date: function (start, end, ev) {
            return scheduler.templates.event_date(start) + " ";
        },
        event_bar_text: function (start, end, ev) {
            return ev.text;
        },
        month_events_link: function (date, count) {
            return "<a>View more(" + count + " events)</a>";
        },
        drag_marker_class: function (start, end, event) {
            return "";
        },
        drag_marker_content: function (start, end, event) {
            return "";
        },
        event_desc: function (start, end, ev) {
            var descText = '';
            if (ev.appt && ev.appt.procedureNames) {
                descText = ev.appt.procedureNames.join(',');
            }
            return descText;
        }
    });
    this.callEvent("onTemplatesReady", []);
};



scheduler.uid = function () {
    if (!this._seed) this._seed = (new Date()).valueOf();
    return this._seed++;
};
scheduler._events = {};
scheduler.clearAll = function () {
    this._events = {};

    this._edit_id = null;
    this._select_id = null;
    this._drag_id = null;
    this._drag_mode = null;
    this._drag_pos = null;

    this.clear_view();
    this.callEvent("onClearAll", []);
};
scheduler.addEvent = function (start_date, end_date, text, id, extra_data) {
    if (!arguments.length)
        return this.addEventNow();
    var ev = start_date;
    if (arguments.length != 1) {
        ev = extra_data || {};
        ev.start_date = start_date;
        ev.end_date = end_date;
        ev.text = text;
        ev.id = id;
    }
    ev.id = ev.id || scheduler.uid();
    ev.text = ev.text || "";

    if (typeof ev.start_date == "string") ev.start_date = this.templates.api_date(ev.start_date);
    if (typeof ev.end_date == "string") ev.end_date = this.templates.api_date(ev.end_date);

    var d = (this.config.event_duration || this.config.time_step) * 60000;
    if (ev.start_date.valueOf() == ev.end_date.valueOf())
        ev.end_date.setTime(ev.end_date.valueOf() + d);

    ev._timed = this.isOneDayEvent(ev);

    var is_new = !this._events[ev.id];
    this._events[ev.id] = ev;
    this.event_updated(ev);
    if (!this._loading)
        this.callEvent(is_new ? "onEventAdded" : "onEventChanged", [ev.id, ev]);
    return ev.id;
};
scheduler.deleteEvent = function (id, silent) {
    var ev = this._events[id];
    if (!silent && (!this.callEvent("onBeforeEventDelete", [id, ev]) || !this.callEvent("onConfirmedBeforeEventDelete", [id, ev])))
        return;
    if (ev) {
        this._select_id = null;
        delete this._events[id];
        this.event_updated(ev);
    }

    this.callEvent("onEventDeleted", [id, ev]);
};
scheduler.getEvent = function (id) {
    return this._events[id];
};
scheduler.setEvent = function (id, hash) {
    if (!hash.id)
        hash.id = id;

    this._events[id] = hash;
};
scheduler.for_rendered = function (id, method) {
    for (var i = this._rendered.length - 1; i >= 0; i--)
        if (this._rendered[i].getAttribute("event_id") == id)
            method(this._rendered[i], i);
};
scheduler.changeEventId = function (id, new_id) {
    if (id == new_id) return;
    var ev = this._events[id];
    if (ev) {
        ev.id = new_id;
        this._events[new_id] = ev;
        delete this._events[id];
    }
    this.for_rendered(id, function (r) {
        r.setAttribute("event_id", new_id);
    });
    if (this._select_id == id) this._select_id = new_id;
    if (this._edit_id == id) this._edit_id = new_id;
    //if (this._drag_id==id) this._drag_id=new_id;
    this.callEvent("onEventIdChange", [id, new_id]);
};

(function () {
    var attrs = ["text", "Text", "start_date", "StartDate", "end_date", "EndDate"];
    var create_getter = function (name) {
        return function (id) { return (scheduler.getEvent(id))[name]; };
    };
    var create_setter = function (name) {
        return function (id, value) {
            var ev = scheduler.getEvent(id);
            ev[name] = value;
            ev._changed = true;
            ev._timed = this.isOneDayEvent(ev);
            scheduler.event_updated(ev, true);
        };
    };
    for (var i = 0; i < attrs.length; i += 2) {
        scheduler["getEvent" + attrs[i + 1]] = create_getter(attrs[i]);
        scheduler["setEvent" + attrs[i + 1]] = create_setter(attrs[i]);
    }
})();

scheduler.event_updated = function (ev, force) {
    if (this.is_visible_events(ev))
        this.render_view_data();
    else
        this.clear_event(ev.id);
};
scheduler.is_visible_events = function (ev) {
    //if in displayed dates
    var in_visible_range = (ev.start_date < this._max_date && this._min_date < ev.end_date);

    if (in_visible_range) {

        //end dates are not between last/first hours
        // Update by mason
        //var end_dates_visible = (this._table_view || ((ev.end_date.getHours() >= this.config.first_hour && ev.end_date.getHours() < this.config.last_hour) ||
        //					(ev.start_date.getHours() >= this.config.first_hour && ev.start_date.getHours() < this.config.last_hour)));
        var end_dates_visible = (this._table_view || ((ev.start_date.getHours() >= this.config.first_hour && ev.start_date.getHours() < this.config.last_hour)));

        if (end_dates_visible) {
            return true;
        } else {

            //event is bigger than area hidden between last/first hours
            var event_duration = (ev.end_date.valueOf() - ev.start_date.valueOf()) / (1000 * 60 * 60),//hours
                hidden_duration = 24 - (this.config.last_hour - this.config.first_hour);

            return (event_duration > hidden_duration);

        }
    } else {
        return false;
    }
};
scheduler.isOneDayEvent = function (ev) {
    var delta = ev.end_date.getDate() - ev.start_date.getDate();

    if (!delta)
        return ev.start_date.getMonth() == ev.end_date.getMonth() && ev.start_date.getFullYear() == ev.end_date.getFullYear();
    else {
        if (delta < 0) delta = Math.ceil((ev.end_date.valueOf() - ev.start_date.valueOf()) / (24 * 60 * 60 * 1000));
        return (delta == 1 && !ev.end_date.getHours() && !ev.end_date.getMinutes() && (ev.start_date.getHours() || ev.start_date.getMinutes()));
    }

};
scheduler.get_visible_events = function (only_timed) {
    //not the best strategy for sure
    var stack = [];

    for (var id in this._events)
        if (this.is_visible_events(this._events[id]))
            if (!only_timed || this._events[id]._timed)
                if (this.filter_event(id, this._events[id]))
                    stack.push(this._events[id]);

    return stack;
};
scheduler.filter_event = function (id, ev) {
    var filter = this["filter_" + this._mode];
    return (filter) ? filter(id, ev) : true;
};
scheduler._is_main_area_event = function (ev) {
    return !!ev._timed;
};
scheduler.render_view_data = function (evs, hold) {
    if (!evs) {
        if (this._not_render) {
            this._render_wait = true;
            return;
        }
        this._render_wait = false;

        this.clear_view();
        evs = this.get_visible_events(!(this._table_view || this.config.multi_day));
    }
    for (var i = 0, len = evs.length; i < len; i++) {
        this._recalculate_timed(evs[i]);
    }

    if (this.config.multi_day && !this._table_view) {

        var tvs = [];
        var tvd = [];
        for (var i = 0; i < evs.length; i++) {
            if (this._is_main_area_event(evs[i]))
                tvs.push(evs[i]);
            else
                tvd.push(evs[i]);
        }

        // multiday events
        if (this._els['dhx_multi_day'] != null) {
            this._rendered_location = this._els['dhx_multi_day'][0];
        }
        this._table_view = true;
        this.render_data(tvd, hold);
        this._table_view = false;

        // normal events
        this._rendered_location = this._els['dhx_cal_data'][0];
        this._table_view = false;
        this.render_data(tvs, hold);

    } else {
        this._rendered_location = this._els['dhx_cal_data'][0];
        this.render_data(evs, hold);
    }
};


scheduler._view_month_day = function (e) {
    var date = scheduler.getActionData(e).date;
    if (!scheduler.callEvent("onViewMoreClick", [date]))
        return;
    scheduler.setCurrentView(date, "day");
};

scheduler._render_month_link = function (ev) {
    var parent = this._rendered_location;
    var toRender = this._lame_clone(ev);

    //render links in each cell of multiday events
    for (var d = ev._sday; d < ev._eday; d++) {

        toRender._sday = d;
        toRender._eday = d + 1;

        var date = scheduler.date;
        var curr = scheduler._min_date;
        curr = date.add(curr, toRender._sweek, "week");
        curr = date.add(curr, toRender._sday, "day");
        var count = scheduler.getEvents(curr, date.add(curr, 1, "day")).length;

        var pos = this._get_event_bar_pos(toRender);
        var widt = (pos.x2 - pos.x);

        var el = document.createElement("div");
        el.onclick = function (e) { scheduler._view_month_day(e || event); };
        el.className = "dhx_month_link";
        el.style.top = pos.y + "px";
        el.style.left = pos.x + "px";
        el.style.width = widt + "px";
        el.innerHTML = scheduler.templates.month_events_link(curr, count);
        this._rendered.push(el);

        parent.appendChild(el);
    }
};

scheduler._recalculate_timed = function (id) {
    if (!id) return;
    var ev;
    if (typeof (id) != "object")
        ev = this._events[id];
    else
        ev = id;
    if (!ev) return;
    ev._timed = scheduler.isOneDayEvent(ev);
};
scheduler.attachEvent("onEventChanged", scheduler._recalculate_timed);
scheduler.attachEvent("onEventAdded", scheduler._recalculate_timed);

scheduler.render_data = function (evs, hold) {
    evs = this._pre_render_events(evs, hold);

    for (var i = 0; i < evs.length; i++)
        if (this._table_view) {
            if (scheduler._mode != 'month') {
                this.render_event_bar(evs[i]);//may be multiday section on other views
            } else {

                var max_evs = scheduler.config.max_month_events;
                if (max_evs !== max_evs * 1 || evs[i]._sorder < max_evs) {
                    //of max number events per month cell is set and event can be rendered
                    this.render_event_bar(evs[i]);
                } else if (max_evs !== undefined && evs[i]._sorder == max_evs) {
                    //render 'view more' links
                    scheduler._render_month_link(evs[i]);
                } else {
                    //do not render events with ordinal number > maximum events per cell
                }
            }



        } else
            this.render_event(evs[i]);
};

scheduler._get_first_visible_cell = function (cells) {
    for (var i = 0; i < cells.length; i++) {
        if ((cells[i].className || "").indexOf("dhx_scale_ignore") == -1) {
            return cells[i];
        }
    }
    // if no visible cell found, return cells[0] to be more tolerant, since it's the original logic
    return cells[0];
};

scheduler._pre_render_events = function (evs, hold) {
    var hb = this.xy.bar_height;
    var h_old = this._colsS.heights;
    var h = this._colsS.heights = [0, 0, 0, 0, 0, 0, 0];
    var data = this._els["dhx_cal_data"][0];

    if (!this._table_view)
        evs = this._pre_render_events_line(evs, hold); //ignore long events for now
    else
        evs = this._pre_render_events_table(evs, hold);

    if (this._table_view) {
        if (hold)
            this._colsS.heights = h_old;
        else {
            var evl = data.firstChild;
            if (evl.rows) {
                for (var i = 0; i < evl.rows.length; i++) {
                    h[i]++;
                    var cells = evl.rows[i].cells;
                    var cellHeight = this._colsS.height - this.xy.month_head_height;
                    if ((h[i]) * hb > cellHeight) { // 22 - height of cell's header
                        //we have overflow, update heights

                        var cHeight = cellHeight;
                        if (this.config.max_month_events * 1 !== this.config.max_month_events || h[i] <= this.config.max_month_events) {
                            cHeight = h[i] * hb;
                        } else if ((this.config.max_month_events + 1) * hb > cellHeight) {
                            cHeight = (this.config.max_month_events + 1) * hb;
                        }

                        for (var j = 0; j < cells.length; j++) {
                            cells[j].childNodes[1].style.height = cHeight + "px";
                        }
                        //	h[i] = (h[i - 1] || 0) + cells[0].offsetHeight;
                    }

                    h[i] = (h[i - 1] || 0) + scheduler._get_first_visible_cell(cells).offsetHeight;
                }
                h.unshift(0);
                if (evl.parentNode.offsetHeight < evl.parentNode.scrollHeight && !scheduler._colsS.scroll_fix && scheduler.xy.scroll_width) {

                    var scale_settings = scheduler._colsS,
                        sum_width = scale_settings[scale_settings.col_length],
                        row_heights = scale_settings.heights.slice();

                    sum_width -= (scheduler.xy.scroll_width || 0);
                    this._calc_scale_sizes(sum_width, this._min_date, this._max_date);
                    scheduler._colsS.heights = row_heights;

                    this.set_xy(this._els["dhx_cal_header"][0], sum_width, this.xy.scale_height);
                    scheduler._render_scales(this._els["dhx_cal_header"][0]);
                    scheduler._render_month_scale(this._els["dhx_cal_data"][0], this._get_timeunit_start(), this._min_date);

                    scale_settings.scroll_fix = true;
                }
            } else {
                //if (!evs.length && this._els["dhx_multi_day"][0].style.visibility == "visible")
                //    h[0] = -1;
                if (evs.length || h[0] == -1) {
                    //shift days to have space for multiday events
                    var childs = evl.parentNode.childNodes;

                    // +1 so multiday events would have 2px from top and 2px from bottom by default
                    var full_multi_day_height = (h[0] + 1) * hb + 1;

                    var used_multi_day_height = full_multi_day_height;
                    var used_multi_day_height_css = full_multi_day_height + "px";
                    if (this.config.multi_day_height_limit) {
                        used_multi_day_height = Math.min(full_multi_day_height, this.config.multi_day_height_limit);
                        used_multi_day_height_css = used_multi_day_height + "px";
                    }

                    data.style.top = (this._els["dhx_cal_navline"][0].offsetHeight + this._els["dhx_cal_header"][0].offsetHeight + used_multi_day_height) + 'px';
                    data.style.height = (this._obj.offsetHeight - parseInt(data.style.top, 10) - (this.xy.margin_top || 0)) + 'px';

                    var multi_day_section = this._els["dhx_multi_day"][0];
                    multi_day_section.style.height = used_multi_day_height_css;
                    multi_day_section.style.visibility = (h[0] == -1 ? "hidden" : "visible");

                    // icon
                    var multi_day_icon = this._els["dhx_multi_day"][1];
                    multi_day_icon.style.height = used_multi_day_height_css;
                    multi_day_icon.style.visibility = (h[0] == -1 ? "hidden" : "visible");
                    multi_day_icon.className = h[0] ? "dhx_multi_day_icon" : "dhx_multi_day_icon_small";
                    this._dy_shift = (h[0] + 1) * hb;
                    if (this.config.multi_day_height_limit) {
                        this._dy_shift = Math.min(this.config.multi_day_height_limit, this._dy_shift);
                    }
                    h[0] = 0;

                    if (used_multi_day_height != full_multi_day_height) {
                        data.style.top = (parseInt(data.style.top) + 2) + "px";

                        multi_day_section.style.overflowY = "auto";
                        //	multi_day_section.style.width = (parseInt(this._els["dhx_cal_navline"][0].style.width)) + "px";

                        multi_day_icon.style.position = "fixed";
                        multi_day_icon.style.top = "";
                        multi_day_icon.style.left = "";
                    }
                }
            }
        }
    }

    return evs;
};
scheduler._get_event_sday = function (ev) {
    return Math.floor((ev.start_date.valueOf() - this._min_date.valueOf()) / (24 * 60 * 60 * 1000));
};
scheduler._get_event_mapped_end_date = function (ev) {
    var end_date = ev.end_date;
    if (this.config.separate_short_events) {
        var ev_duration = (ev.end_date - ev.start_date) / 60000; // minutes
        if (ev_duration < this._min_mapped_duration) {
            end_date = this.date.add(end_date, this._min_mapped_duration - ev_duration, "minute");
        }
    }
    return end_date;
};
scheduler._pre_render_events_line = function (evs, hold) {
    // Added by Mason
    var originalEvents = _.clone(evs);
    var mockEvents = [];
    evs.length = 0;
    _.forEach(originalEvents, function (ev) {
        if (ev.isMock) {
            ev._count = 1;
            ev._sorder = 0;
            ev._sday = scheduler._get_event_sday(ev);
            mockEvents.push(ev);
        } else {
            evs.push(ev);
        }
    });

    var evs_originals = [];
    this._min_mapped_duration = Math.ceil(this.xy.min_event_height * 60 / this.config.hour_size_px);  // values could change along the way

    for (var i = 0; i < evs.length; i++) {
        var ev = evs[i];

        //check date overflow
        var sd = ev.start_date;
        var ed = ev.end_date;
        //check scale overflow
        var sh = sd.getHours();
        var eh = ed.getHours();

        ev._sday = this._get_event_sday(ev); // sday based on event start_date
        if (this._ignores[ev._sday]) {
            //ignore event
            evs.splice(i, 1);
            i--;
            continue;
        }

        if (sh < this.config.first_hour
            || (eh >= this.config.last_hour && ed.getMinutes() > 0)) {
            // Need to create copy of event as we will be changing it's start/end date
            // e.g. first_hour = 11 and event.start_date hours = 9. Need to preserve that info
            evs_originals.push(ev);
            evs[i] = ev = this._copy_event(ev);

            if (sh < this.config.first_hour) {
                ev.start_date.setHours(this.config.first_hour);
                ev.start_date.setMinutes(0);
            }
            if (eh >= this.config.last_hour) {
                ev.end_date.setMinutes(0);
                ev.end_date.setHours(this.config.last_hour);
            }

            if (ev.start_date > ev.end_date || sh == this.config.last_hour) {
                evs.splice(i, 1);
                i--;
                continue;
            }
        }
    }

    if (!hold) {
        if (this._drag_id && evs && evs.length > 0) {
            var dragEvent = evs[evs.length - 1];
            if (dragEvent.id === this._drag_id) {
                dragEvent._columnCnt = 1;
                dragEvent._column = 1;
                dragEvent._columnSpan = 1;
            }
        } else {
            var layoutEngine = new DayEventLayoutEngine(scheduler.formatDayEvent,
                {
                    isLogEnabled: false,
                    isDumpEnabled: false,
                    isAdjustFromLeftToRight: true,
                    isAdjustFromRightToLeft: true
                });
            layoutEngine.run(evs);
            evs.forEach(function (theEvent) {
                if (theEvent._isDummy && theEvent._originalEvent) {
                    theEvent._originalEvent._column = theEvent._column;
                    theEvent._originalEvent._columnCnt = theEvent._columnCnt;
                    theEvent._originalEvent._columnSpan = theEvent._columnSpan;
                }
            });
        }
    }
    //return evs;
    return originalEvents;
};
scheduler._time_order = function (evs) {
    evs.sort(function (a, b) {
        if (a.start_date.valueOf() == b.start_date.valueOf()) {
            if (a._timed && !b._timed) return 1;
            if (!a._timed && b._timed) return -1;
            return a.id > b.id ? 1 : -1;
        }
        return a.start_date > b.start_date ? 1 : -1;
    });
};
scheduler._pre_render_events_table = function (evs, hold) { // max - max height of week slot
    this._time_order(evs);
    var out = [];
    var weeks = [
        [],
        [],
        [],
        [],
        [],
        [],
        []
    ]; //events by weeks
    var max = this._colsS.heights;
    var start_date;
    var cols = this._cols.length;
    var chunks_info = {};

    for (var i = 0; i < evs.length; i++) {
        var ev = evs[i];
        var id = ev.id;
        if (!chunks_info[id]) {
            chunks_info[id] = {
                first_chunk: true,
                last_chunk: true
            };
        }
        var chunk_info = chunks_info[id];
        var sd = (start_date || ev.start_date);
        var ed = ev.end_date;
        //trim events which are crossing through current view
        if (sd < this._min_date) {
            chunk_info.first_chunk = false;
            sd = this._min_date;
        }
        if (ed > this._max_date) {
            chunk_info.last_chunk = false;
            ed = this._max_date;
        }

        var locate_s = this.locate_holder_day(sd, false, ev);
        ev._sday = locate_s % cols;

        //skip single day events for ignored dates
        if (this._ignores[ev._sday] && ev._timed) continue;

        var locate_e = this.locate_holder_day(ed, true, ev) || cols;
        ev._eday = (locate_e % cols) || cols; //cols used to fill full week, when event end on monday
        ev._length = locate_e - locate_s;

        //3600000 - compensate 1 hour during winter|summer time shift
        ev._sweek = Math.floor((this._correct_shift(sd.valueOf(), 1) - this._min_date.valueOf()) / (60 * 60 * 1000 * 24 * cols));

        //current slot
        var stack = weeks[ev._sweek];
        //check order position
        var stack_line;

        for (stack_line = 0; stack_line < stack.length; stack_line++)
            if (stack[stack_line]._eday <= ev._sday)
                break;

        if (!ev._sorder || !hold) {
            ev._sorder = stack_line;
        }

        if (ev._sday + ev._length <= cols) {
            start_date = null;
            out.push(ev);
            stack[stack_line] = ev;
            //get max height of slot
            max[ev._sweek] = stack.length - 1;
            ev._first_chunk = chunk_info.first_chunk;
            ev._last_chunk = chunk_info.last_chunk;
        } else { // split long event in chunks
            var copy = this._copy_event(ev);
            copy.id = ev.id;
            copy._length = cols - ev._sday;
            copy._eday = cols;
            copy._sday = ev._sday;
            copy._sweek = ev._sweek;
            copy._sorder = ev._sorder;
            copy.end_date = this.date.add(sd, copy._length, "day");
            copy._first_chunk = chunk_info.first_chunk;
            if (chunk_info.first_chunk) {
                chunk_info.first_chunk = false;
            }

            out.push(copy);
            stack[stack_line] = copy;
            start_date = copy.end_date;
            //get max height of slot
            max[ev._sweek] = stack.length - 1;
            i--;
            continue;  //repeat same step
        }
    }
    return out;
};
scheduler._copy_dummy = function () {
    var a = new Date(this.start_date);
    var b = new Date(this.end_date);
    this.start_date = a;
    this.end_date = b;
    this._isDummy = true;
};
scheduler._copy_event = function (ev) {
    this._copy_dummy.prototype = ev;
    var dummyEvent = new this._copy_dummy();
    dummyEvent._originalEvent = ev;
    return dummyEvent;
    //return {start_date:ev.start_date, end_date:ev.end_date, text:ev.text, id:ev.id}
};
scheduler._rendered = [];
scheduler.clear_view = function () {
    for (var i = 0; i < this._rendered.length; i++) {
        var obj = this._rendered[i];
        if (obj.parentNode) obj.parentNode.removeChild(obj);
    }
    this._rendered = [];
};
scheduler.updateEvent = function (id) {
    var ev = this.getEvent(id);
    this.clear_event(id);

    if (ev && this.is_visible_events(ev) && this.filter_event(id, ev) && (this._table_view || this.config.multi_day || ev._timed)) {
        if (this.config.update_render) {
            this.render_view_data();
        } else {
            if (this.getState().mode == "month" && !this.getState().drag_id && !this.isOneDayEvent(ev)) {
                this.render_view_data();
            } else {
                this.render_view_data([ev], true);
            }
        }
    }
};
scheduler.clear_event = function (id) {
    this.for_rendered(id, function (node, i) {
        if (node.parentNode)
            node.parentNode.removeChild(node);
        scheduler._rendered.splice(i, 1);
    });
};
scheduler._y_from_date = function (date) {
    var sm = date.getHours() * 60 + date.getMinutes();
    return ((Math.round((sm * 60 * 1000 - this.config.first_hour * 60 * 60 * 1000) * this.config.hour_size_px / (60 * 60 * 1000))) % (this.config.hour_size_px * 24)); //42px/hour
};
scheduler._calc_event_y = function (ev, min_height) {
    min_height = min_height || 0;
    var top = this._y_from_date(ev.start_date);
    var sm = ev.start_date.getHours() * 60 + ev.start_date.getMinutes();
    var em = Math.min((ev.end_date.getHours() * 60 + ev.end_date.getMinutes()), this.config.last_minute);
    var duration = em - sm;
    if (duration < 0) {
        duration = moment(ev.end_date).diff(ev.start_date, 'minutes');
    }
    var height = Math.max(min_height, duration * this.config.hour_size_px / 60);
    return {
        top: top,
        height: height
    };
};
scheduler.render_event = function (ev) {
    var menu = scheduler.xy.menu_width;
    var menu_offset = (this.config.use_select_menu_space) ? 0 : menu;
    if (ev._sday < 0) return; //can occur in case of recurring event during time shift

    var parent = scheduler.locate_holder(ev._sday);
    if (!parent) return; //attempt to render non-visible event

    var pos_y = this._calc_event_y(ev, scheduler.xy.min_event_height);
    var top = pos_y.top,
        height = pos_y.height;

    // var ev_count = ev._count || 1;
    // var ev_sorder = ev._sorder || 0;

    // var width = Math.floor((parent.clientWidth - menu_offset) / ev_count);
    // var left = ev_sorder * width + 1;

    var ev_count = ev._columnCnt || 1;
    var ev_sorder = (ev._column - 1) || 0;
    var columnWidth = Math.floor((parent.clientWidth - menu_offset) / ev_count);
    var width = columnWidth * ev._columnSpan;
    var left = ev_sorder * columnWidth + 1;


    // if (!ev._inner) width = width * (ev_count - ev_sorder);
    // if (this.config.cascade_event_display) {
    //     var limit = this.config.cascade_event_count;
    //     var margin = this.config.cascade_event_margin;
    //     left = ev_sorder % limit * margin;
    //     var right = (ev._inner) ? (ev_count - ev_sorder - 1) % limit * margin / 2 : 0;
    //     width = Math.floor(parent.clientWidth - menu_offset - left - right);
    // }

    var d = this._render_v_bar(ev, menu_offset + left, top, width, height, ev._text_style,
        scheduler.templates.event_header(ev.start_date, ev.end_date, ev),
        scheduler.templates.event_text(ev.start_date, ev.end_date, ev),
        scheduler.templates.event_desc(ev.start_date, ev.end_date, ev));

    this._rendered.push(d);
    parent.appendChild(d);

    left = left + parseInt(parent.style.left, 10) + menu_offset;

    // Added by Mason
    //d.ondragstart = function (e) {
    //    var eventId = scheduler._locate_event(e.target);
    //    //scheduler._drag_id = eventId;
    //    //scheduler._drag_mode = 'move';
    //    //return;
    //    setTimeout(function () {
    //        var mockEvent = scheduler._create_mock_event(eventId);
    //        scheduler._drag_id = mockEvent.id;
    //        e.dataTransfer.setData("event", mockEvent);
    //        scheduler._drag_mode = 'move';
    //    });
    //};

    if (this._edit_id == ev.id) {
        d.style.zIndex = 1; //fix overlapping issue
        width = Math.max(width - 4, scheduler.xy.editor_width);
        d = document.createElement("DIV");
        d.setAttribute("event_id", ev.id);
        this.set_xy(d, width, height - 20, left, top + 14);
        d.className = "dhx_cal_event dhx_cal_editor";

        var tplClass = scheduler.templates.event_class(ev.start_date, ev.end_date, ev);

        if (tplClass) {
            d.className += " " + tplClass;
        }
        var d2 = document.createElement("DIV");
        this.set_xy(d2, width - 6, height - 26);
        d2.style.cssText += ";margin:2px 2px 2px 2px;overflow:hidden;";

        d.appendChild(d2);
        this._els["dhx_cal_data"][0].appendChild(d);
        this._rendered.push(d);

        d2.innerHTML = "<textarea class='dhx_cal_editor'>" + ev.text + "</textarea>";
        if (this._quirks7) d2.firstChild.style.height = height - 12 + "px"; //IEFIX
        this._editor = d2.firstChild;
        this._editor.onkeydown = function (e) {
            if ((e || event).shiftKey) return true;
            var code = (e || event).keyCode;
            if (code == scheduler.keys.edit_save) scheduler.editStop(true);
            if (code == scheduler.keys.edit_cancel) scheduler.editStop(false);
        };
        this._editor.onselectstart = function (e) {
            (e || event).cancelBubble = true;
            return true;
        };
        scheduler._focus(d2.firstChild, true);
        //IE and opera can add x-scroll during focusing
        this._els["dhx_cal_data"][0].scrollLeft = 0;
    }
    if (this.xy.menu_width !== 0 && this._select_id == ev.id) {
        // Ares removed, don't draw icon panel.
        return;

        if (this.config.cascade_event_display && this._drag_mode)
            d.style.zIndex = 1; //fix overlapping issue for cascade view in case of dnd of selected event
        var icons = this.config["icons_" + ((this._edit_id == ev.id) ? "edit" : "select")];
        var icons_str = "";
        var bg_color = (ev.color ? ("background-color: " + ev.color + ";") : "");
        var color = (ev.textColor ? ("color: " + ev.textColor + ";") : "");
        for (var i = 0; i < icons.length; i++)
            icons_str += "<div class='dhx_menu_icon " + icons[i] + "' style='" + bg_color + "" + color + "' title='" + this.locale.labels[icons[i]] + "'></div>";
        var obj = this._render_v_bar(ev, left - menu + 1, top, menu, icons.length * 20 + 26 - 2, "", "<div style='" + bg_color + "" + color + "' class='dhx_menu_head'></div>", icons_str, true);
        obj.style.left = left - menu + 1;
        this._els["dhx_cal_data"][0].appendChild(obj);
        this._rendered.push(obj);
    }
    if (this.config.drag_highlight && this._drag_id == ev.id) {
        this.highlightEventPosition(ev);
    }
};

scheduler._render_v_bar = function (ev, x, y, w, h, style, contentA, contentB, contentC, bottom) {
    contentB = Ares.aresSafeHtml(contentB);
    var d = document.createElement("DIV");
    var id = ev.id;
    var cs = (bottom) ? "dhx_cal_event dhx_cal_select_menu" : "dhx_cal_event";
    cs += " appt";
    cs += (h < 70 || w < 80) ? ' appt-small' : ' appt-large';
    if (ev.isRebooking) {
        cs += ' appt-rebooking';
    }
    // 非当前诊所预约，设置为只读
    if (!ev.readonly && ev.appt && (ev.appt.isAtCurrentOffice === false || ev.appt.isPlaceholder)) {
        ev.readonly = true;
    }

    if (id == this._select_id) {
        cs += ' appt-selected';
    }
    if (ev.stateClassName) {
        cs += ' ' + ev.stateClassName;
    } else {
        cs += ' appt-unscheduled';
    }
    if (ev.readonly) {
        cs += ' appt-readonly';
    }
    if (ev.appt && ev.appt.sourceType && ev.channels && ev.channels.indexOf(ev.appt.sourceType.toLowerCase()) >= 0) {
        cs += ' appt-third-party';
    }
    var cse = scheduler.templates.event_class(ev.start_date, ev.end_date, ev);
    if (cse) cs = cs + " " + cse;
    var bg_color = (ev.procedureColor ? ("background:" + ev.procedureColor + ";") : "");
    var color = 'white';
    if (!this.isStateTheme && ev.procedureColor) {
        color = Ares.getForegroundColor(ev.procedureColor);
    } else if (ev.readonly) {
        color = 'black';
    }
    color = 'color:' + color + ";";

    if (ev.isPlaceHolder) {
        bg_color = "background:#A0A0A0 !important;";
    }
    h -= 3;
    var html = '<div id="event' + id + '" event_id="' + id + '" class="' + cs + '" style="top:' + y + 'px; left:' + x + 'px; width:' + (w - 4) + 'px; height:' + h + 'px;' + bg_color + '' + color + (style || "") + '" is_place_holder="' + ev.isPlaceHolder + '"></div>';
    d.innerHTML = html;

    var container = d.cloneNode(true).firstChild;

    if (!bottom && scheduler.renderEvent(container, ev, w, h, contentA, contentB)) {
        return container;
    } else {
        container = d.firstChild;

        var isShowTitle = h >= 15 && w > 20;
        var isShowSubTitle = w > 150;
        var isShowIcon = isShowTitle && w > 50;
        var isShowContent = h > 30 && w > 50;
        var isShowFooter = h > 50 || w > 80;
        var isSignleTimeSlot = (h <= scheduler.config.time_slot_height);
        var isShowStateIcon = isSignleTimeSlot && w > 100;
        var isContentEmpty = contentC == null || contentC == '';
        var isShowTime = (h > 50 && w > 120) || (isShowContent && (isContentEmpty || w > 80));
        var isShowCountDownHead = (h < 35 && w > 145);
        var isShowCountDownBody = (h > 45 && w > 100);
        var apptHeader = '';
        var apptBody = '';
        var apptContent = '';
        var apptFooter = '';
        var apptResizer = '';
        var apptDragger = '';
        var apptMask = '';
        var headerColor = null;

        var isShowCaseNo = scheduler.config.is_show_caseno && w > 140 && isShowTime;
        if (!ev.appt)
            isShowCaseNo = false;
        var isShowApptNotes = scheduler.config.is_show_notes;
        if (!ev.appt || !ev.appt.notes)
            isShowApptNotes = false;

        var minHeaderHeight = Math.min(h, 20);
        var headerStyle = 'min-height:' + minHeaderHeight + 'px;';
        if (this.isProcedureTheme || isSignleTimeSlot) {
            headerStyle += color;
            if (isSignleTimeSlot) {
                headerStyle += bg_color;
            }
            headerColor = color;
        }
        if (h <= 30) {
            // 解决圆角颜色没有完全覆盖
            headerStyle += 'border-bottom-right-radius:10px;';
        }

        if (ev.appt && ev.appt.patient) {
            var sexHtml = '';
            if (ev.appt.patient.sex == 2) {
                sexHtml = '<i class="iconfont icon-nv sex-icon nv"></i>';
            } else if (ev.appt.patient.sex == 1) {
                sexHtml = '<i class="iconfont icon-nan sex-icon nan"></i>';
            }
            contentB += sexHtml;
        }

        apptHeader = '<div class="appt-header" style="' + headerStyle + '">';
        if (isShowTitle) {
            if (isShowStateIcon && ev.stateAbbr) {
                var apptStateIconClass = 'appt-state-icon';
                if (ev.stateClassName) {
                    apptStateIconClass += ' ' + ev.stateClassName;
                }
                apptHeader += '<div class="' + apptStateIconClass + '">' + ev.stateAbbr + '</div>';
            }
            if (isShowCountDownHead && ev.isPlaceHolder) {
                var overtimeContent = '<span id="overtime-container' + ev.id + '">';
                if (ev.overtimeHint) {
                    overtimeContent += '<i class="iconfont icon-59 icon-style"></i>'
                        + ev.overtimeHint;
                }
                overtimeContent += '</span>';
                contentB += overtimeContent;
            }
            apptHeader += '<span class="appt-title">' + contentB + '</span>';
            if (isShowSubTitle) {
                apptHeader += (ev.subTitle ? '<span class="appt-sub-title"> [' + ev.subTitle + ']</span>' : '')
            }
        }
        apptHeader += '</div>';
        //预约项内容根据计算高度自动显示内容
        if (isShowContent && !isContentEmpty) {
            //计算预约项显示区域高度
            var contentHeight = h - 20;
            if (isShowApptNotes)
                contentHeight -= 18;
            if (isShowCaseNo || isShowTime)
                contentHeight -= 20;
            //计算所得高度只够显示一行数据时优先显示预约项内容
            if (contentHeight < 10) {
                isShowFooter = false;
                contentHeight += 20;
            }
            apptContent += '<div class="appt-content" style="line-height:16px;' + color + ';height:' + contentHeight + 'px;">' + contentC + '</div>';
        }
        if (isShowApptNotes) {
            if (ev.appt && ev.appt.sourceType === "wesure" && isShowCountDownBody && ev.isPlaceHolder) {
                apptContent += '';
            } else {
                apptContent += '<div class="appt-content-remark"  style="' + color + '">' + ev.appt.notes + '</div>';
            }
        }
        if (isShowCountDownBody && ev.isPlaceHolder) {
            var overtimeContent = '<div id="overtime-container' + ev.id + '" class="text-style">';
            if (ev.overtimeHint) {
                overtimeContent += '<i class="iconfont icon-59 icon-style"></i>'
                    + ev.overtimeHint;
            }
            overtimeContent += '</div>';
            // apptContent += '<div id="overtime-container' + ev.id +'"></div>';
            apptContent += overtimeContent;
        }
        //apptBody = '<div class="dhx_body appt-body" style="position:absolute; padding:0; width:' + (w - (this._quirks ? 0 : 4)) + 'px; height:' + (h - (this._quirks ? -1 : 0) + 1) + 'px;">'
        apptBody = '<div class="dhx_body appt-body" style="position:absolute; padding:0;">'
            + apptHeader + apptContent
            + '</div>'; // +2 css specific, moved from render_event

        var draggerClass = ev.readonly ? 'appt-dragger' : 'dhx_event_move dhx_title appt-dragger';
        var draggerHeight = ev.isFake ? '100%' : '20px';
        var iconHtml = isShowIcon ? scheduler._render_icon(ev, w) : '';
        apptDragger = '<div class="' + draggerClass + '" style="height:' + draggerHeight + ';width:' + (w - 4) + 'px;' + headerColor + '">'
            + iconHtml
            + '</div>';

        if (isShowFooter) {
            apptFooter = "";
            if (!isShowTime) {
                contentA = '';
            }
            if (isShowCaseNo && !ev.appt.isPlaceholder) {
                apptFooter += '<div class="appt-footer-left" style="width:' + (w - 120) + 'px;' + color + '">' + ev.appt.patient.privateId + '</div>';
            }
            apptFooter += '<div class="appt-footer" style="width:' + (w - 4) + 'px;' + color + '">' + contentA + '</div>';
        }

        var resizerClass = ev.readonly ? 'appt-resizer' : 'dhx_event_resize dhx_footer appt-resizer';
        if (bottom) {
            resizerClass = "dhx_resize_denied " + resizerClass;
        }

        apptResizer = '<div class="' + resizerClass + '" style="width:' + (w - 8) + 'px;' + (bottom ? ' margin-top:-1px;' : '') + '" ></div>';

        if (ev.appt && ev.appt.isAtCurrentOffice === false) {
            //apptMask = '<div class="ares-mask-layer"></div>'
        }

        var apptHtml = "";
        if (ev.isPlaceHolder && !ev.appt.isPlaceholder) {
            apptHtml = apptFooter + apptResizer + apptMask;
        }
        else if (ev.isPlaceHolder && ev.appt.isPlaceholder) {
            apptHtml = apptBody + apptFooter + apptResizer + apptMask;
        }
        else {
            apptHtml = apptBody + apptDragger + apptFooter + apptResizer + apptMask;
        }
        container.innerHTML = apptHtml;
    }

    return container;
};

// Added by Mason
scheduler._render_icon = function (ev, width) {
    if (!ev.appt) {
        return '';
    }
    var html = '<div class="appt-icon-container pull-right">';
    var visibleIconCnt = Math.max(Math.floor((width - 50) / 20), 0);
    if (visibleIconCnt && ev.appt.notes && ev.appt.notes.length > 0) {
        html += '<div class="appt-icon appt-icon-notes" title="' + ev.appt.notes + '">备</div>';
        visibleIconCnt--;
    }

    if (visibleIconCnt) {
        var ageAbbr = this._aresGetAgeAbbr(ev);
        if (ageAbbr == '儿') {
            html += '<div class="appt-icon appt-icon-child" title="儿童">儿</div>';
            visibleIconCnt--;
        } else if (ageAbbr == '老') {
            html += '<div class="appt-icon appt-icon-old" title="老人">老</div>';
            visibleIconCnt--;
        }
    }

    if (visibleIconCnt && ev.appt.checkInType == '初诊') {
        html += '<div class="appt-icon appt-icon-first-vist" title="初诊">初</div>';
        visibleIconCnt--;
    }

    html += '</div>';
    return html;
};

scheduler._aresGetAgeAbbr = function (ev) {
    var ageAbbr = ev.appt.ageAbbr;
    if (!ageAbbr && ev.appt.patient && ev.appt.patient.birth) {
        ageAbbr = moment(ev.appt.patient.birth).toDate().getAgeAbbr();
    }
    return ageAbbr;
};

scheduler.renderEvent = function () {
    return false;
};
scheduler.locate_holder = function (day) {
    if (this._mode == "day") return this._els["dhx_cal_data"][0].firstChild; //dirty
    return this._els["dhx_cal_data"][0].childNodes[day];
};
scheduler.locate_holder_day = function (date, past) {
    var day = Math.floor((this._correct_shift(date, 1) - this._min_date) / (60 * 60 * 24 * 1000));
    //when locating end data of event , we need to use next day if time part was defined
    if (past && this.date.time_part(date)) day++;
    return day;
};



scheduler._get_dnd_order = function (order, ev_height, max_height) {
    if (!this._drag_event)
        return order;
    if (!this._drag_event._orig_sorder)
        this._drag_event._orig_sorder = order;
    else
        order = this._drag_event._orig_sorder;

    var evTop = ev_height * order;
    while ((evTop + ev_height) > max_height) {
        order--;
        evTop -= ev_height;
    }
    order = Math.max(order, 0);
    return order;
};
//scheduler._get_event_bar_pos = function(sday, eday, week, drag){
scheduler._get_event_bar_pos = function (ev) {
    var x = this._colsS[ev._sday];
    var x2 = this._colsS[ev._eday];
    if (x2 == x) x2 = this._colsS[ev._eday + 1];
    var hb = this.xy.bar_height;

    var order = ev._sorder;
    if (ev.id == this._drag_id) {
        var cellHeight = this._colsS.heights[ev._sweek + 1] - this._colsS.heights[ev._sweek] - this.xy.month_head_height;//22 for month head height
        order = scheduler._get_dnd_order(order, hb, cellHeight);
    }
    var y_event_offset = order * hb;
    var y = this._colsS.heights[ev._sweek] + (this._colsS.height ? (this.xy.month_scale_height + 2) : 2) + y_event_offset;
    return { x: x, x2: x2, y: y };
};

scheduler.render_event_bar = function (ev) {
    var parent = this._rendered_location;
    var pos = this._get_event_bar_pos(ev);

    var y = pos.y;
    var x = pos.x;
    var x2 = pos.x2;

    // resize for month mutliday events
    var resize_handle = "";

    //events in ignored dates

    if (!x2) return;

    var resizable = scheduler.config.resize_month_events && this._mode == "month" &&
        (!ev._timed || scheduler.config.resize_month_timed);

    var d = document.createElement("DIV");
    var left_chunk = (ev.hasOwnProperty("_first_chunk") && ev._first_chunk),
        right_chunk = (ev.hasOwnProperty("_last_chunk") && ev._last_chunk);

    var resize_left = resizable && (ev._timed || left_chunk);
    var resize_right = resizable && (ev._timed || right_chunk);

    var cs = "dhx_cal_event_clear";
    if (!ev._timed || resizable) {
        cs = "dhx_cal_event_line";
    }
    if (left_chunk) {
        cs += " dhx_cal_event_line_start";
    }
    if (right_chunk) {
        cs += " dhx_cal_event_line_end";
    }
    if (resize_left) {
        resize_handle += "<div class='dhx_event_resize dhx_event_resize_start'></div>";
    }
    if (resize_right) {
        resize_handle += "<div class='dhx_event_resize dhx_event_resize_end'></div>";
    }

    var cse = scheduler.templates.event_class(ev.start_date, ev.end_date, ev);
    if (cse) {
        cs += " " + cse;
    }

    var bg_color = (ev.color ? ("background:" + ev.color + ";") : "");
    var color = (ev.textColor ? ("color:" + ev.textColor + ";") : "");

    var style_text = [
        "position:absolute",
        "top:" + y + "px",
        "left:" + x + "px",
        "width:" + (x2 - x - 15) + "px",
        color,
        bg_color,
        (ev._text_style || "")
    ].join(";");

    var html = '<div event_id="' + ev.id + '" class="' + cs + '" style="' + style_text + '" is_place_holder="' + ev.isPlaceHolder + '">';
    if (resizable) {
        html += resize_handle;
    }
    if (scheduler.getState().mode == "month") {
        ev = scheduler.getEvent(ev.id); // ev at this point could be a part (row in a month view) of a larger event
    }

    if (ev._timed)
        html += scheduler.templates.event_bar_date(ev.start_date, ev.end_date, ev);
    html += scheduler.templates.event_bar_text(ev.start_date, ev.end_date, ev) + '</div>';
    html += '</div>';

    d.innerHTML = html;

    this._rendered.push(d.firstChild);
    parent.appendChild(d.firstChild);
};

scheduler._locate_event = function (node) {
    var id = null;
    while (node && !id && node.getAttribute) {
        id = node.getAttribute("event_id");
        node = node.parentNode;
    }
    return id;
};

scheduler._locate_placeholder = function (node) {
    var id = null;
    while (node && !id && node.getAttribute) {
        id = node.getAttribute("is_place_holder");
        node = node.parentNode;
    }
    return id;
};

scheduler.edit = function (id) {
    if (this._edit_id == id) return;
    this.editStop(false, id);
    this._edit_id = id;
    this.updateEvent(id);
};
scheduler.editStop = function (mode, id) {
    if (id && this._edit_id == id) return;
    var ev = this.getEvent(this._edit_id);
    if (ev) {
        if (mode) ev.text = this._editor.value;
        this._edit_id = null;
        this._editor = null;
        this.updateEvent(ev.id);
        this._edit_stop_event(ev, mode);
    }
};
scheduler._edit_stop_event = function (ev, mode) {
    if (this._new_event) {
        if (!mode) {
            if (ev) // in case of custom lightbox user can already delete event
                this.deleteEvent(ev.id, true);
        } else {
            this.callEvent("onEventAdded", [ev.id, ev]);
        }
        this._new_event = null;
    } else {
        if (mode) {
            this.callEvent("onEventChanged", [ev.id, ev]);
        }
    }
};

scheduler.getEvents = function (from, to) {
    var result = [];
    for (var a in this._events) {
        var ev = this._events[a];
        if (ev && ((!from && !to) || (ev.start_date < to && ev.end_date > from)))
            result.push(ev);
    }
    return result;
};
scheduler.getRenderedEvent = function (id) {
    if (!id)
        return;
    var rendered_events = scheduler._rendered;
    for (var i = 0; i < rendered_events.length; i++) {
        var rendered_event = rendered_events[i];
        if (rendered_event.getAttribute("event_id") == id) {
            return rendered_event;
        }
    }
    return null;
};
scheduler.showEvent = function (id, mode) {
    var ev = (typeof id == "number" || typeof id == "string") ? scheduler.getEvent(id) : id;
    mode = mode || scheduler._mode;

    if (!ev || (this.checkEvent("onBeforeEventDisplay") && !this.callEvent("onBeforeEventDisplay", [ev, mode])))
        return;

    var scroll_hour = scheduler.config.scroll_hour;
    scheduler.config.scroll_hour = ev.start_date.getHours();
    var preserve_scroll = scheduler.config.preserve_scroll;
    scheduler.config.preserve_scroll = false;

    var original_color = ev.color;
    var original_text_color = ev.textColor;
    if (scheduler.config.highlight_displayed_event) {
        ev.color = scheduler.config.displayed_event_color;
        ev.textColor = scheduler.config.displayed_event_text_color;
    }

    scheduler.setCurrentView(new Date(ev.start_date), mode);

    ev.color = original_color;
    ev.textColor = original_text_color;
    scheduler.config.scroll_hour = scroll_hour;
    scheduler.config.preserve_scroll = preserve_scroll;

    if (scheduler.matrix && scheduler.matrix[mode]) {
        scheduler._els.dhx_cal_data[0].scrollTop = getAbsoluteTop(scheduler.getRenderedEvent(ev.id)) - getAbsoluteTop(scheduler._els.dhx_cal_data[0]) - 20;
    }

    scheduler.callEvent("onAfterEventDisplay", [ev, mode]);
};

scheduler._append_drag_marker = function (m) {
    if (m.parentNode) return;
    var zone = scheduler._els["dhx_cal_data"][0];

    var scale = zone.lastChild;
    if (scale.className && scale.className.indexOf("dhx_scale_holder") < 0 && scale.previousSibling) {
        scale = scale.previousSibling;
    }
    if (scale && scale.className.indexOf("dhx_scale_holder") === 0) {
        scale.appendChild(m);
    }
};

scheduler._update_marker_position = function (m, event) {
    var size = scheduler._calc_event_y(event, 0);
    m.style.top = size.top + "px";
    m.style.height = size.height + "px";
};

scheduler.highlightEventPosition = function (event) {
    var m = document.createElement("div");

    m.setAttribute("event_id", event.id);
    this._rendered.push(m);
    this._update_marker_position(m, event);

    var css = this.templates.drag_marker_class(event.start_date, event.end_date, event);
    var html = this.templates.drag_marker_content(event.start_date, event.end_date, event);
    m.className = "dhx_drag_marker";
    if (css)
        m.className += " " + css;
    if (html)
        m.innerHTML = html;
    this._append_drag_marker(m);
};

scheduler.on_load = function (loader) {
    var evs;
    var error = false;
    try {
        evs = this[this._process].parse(loader.xmlDoc.responseText);
    } catch (e) {
        error = true;
    }

    if (error) {
        this.callEvent("onLoadError", [loader.xmlDoc]);
        evs = [];
    }

    scheduler._process_loading(evs);

    this.callEvent("onXLE", []);
};
scheduler._process_loading = function (evs) {
    this._loading = true;
    this._not_render = true;
    for (var i = 0; i < evs.length; i++) {
        if (!this.callEvent("onEventLoading", [evs[i]])) continue;
        this.addEvent(evs[i]);
    }
    this._not_render = false;
    if (this._render_wait) this.render_view_data();

    this._loading = false;
    if (this._after_call) this._after_call();
    this._after_call = null;
};
scheduler._init_event = function (event) {
    event.text = (event.text || event._tagvalue) || "";
    event.start_date = scheduler._init_date(event.start_date);
    event.end_date = scheduler._init_date(event.end_date);
};

scheduler._init_date = function (date) {
    if (!date)
        return null;
    if (typeof date == "string")
        return scheduler.templates.xml_date(date);
    else return new Date(date);
};

scheduler.json = {};
scheduler.json.parse = function (data) {
    if (typeof data == "string") {
        scheduler._temp = eval("(" + data + ")");
        data = (scheduler._temp) ? scheduler._temp.data || scheduler._temp.d || scheduler._temp : [];
    }

    if (data.dhx_security)
        dhtmlx.security_key = data.dhx_security;

    var collections = (scheduler._temp && scheduler._temp.collections) ? scheduler._temp.collections : {};
    var collections_loaded = false;
    for (var key in collections) {
        if (collections.hasOwnProperty(key)) {
            collections_loaded = true;
            var collection = collections[key];
            var arr = scheduler.serverList[key];
            if (!arr) continue;
            arr.splice(0, arr.length); //clear old options
            for (var j = 0; j < collection.length; j++) {
                var option = collection[j];
                var obj = { key: option.value, label: option.label }; // resulting option object
                for (var option_key in option) {
                    if (option.hasOwnProperty(option_key)) {
                        if (option_key == "value" || option_key == "label")
                            continue;
                        obj[option_key] = option[option_key]; // obj['value'] = option['value']
                    }
                }
                arr.push(obj);
            }
        }
    }
    if (collections_loaded)
        scheduler.callEvent("onOptionsLoad", []);

    var evs = [];
    for (var i = 0; i < data.length; i++) {
        var event = data[i];
        scheduler._init_event(event);
        evs.push(event);
    }
    return evs;
};
scheduler.parse = function (data, type) {
    this._process = type;
    this.on_load({ xmlDoc: { responseText: data } });
};
//possible values - day,week,month,year,all
scheduler.setLoadMode = function (mode) {
    if (mode == "all") mode = "";
    this._load_mode = mode;
};

scheduler.serverList = function (name, array) {
    if (array) {
        this.serverList[name] = array.slice(0);
        return this.serverList[name];
    }
    this.serverList[name] = (this.serverList[name] || []);
    return this.serverList[name];
};
scheduler._userdata = {};

scheduler._xmlNodeToJSON = function (node) {
    var t = {};
    for (var i = 0; i < node.attributes.length; i++)
        t[node.attributes[i].name] = node.attributes[i].value;

    for (var i = 0; i < node.childNodes.length; i++) {
        var child = node.childNodes[i];
        if (child.nodeType == 1)
            t[child.tagName] = child.firstChild ? child.firstChild.nodeValue : "";
    }

    if (!t.text) t.text = node.firstChild ? node.firstChild.nodeValue : "";

    return t;
};
scheduler.attachEvent("onXLS", function () {
    if (this.config.show_loading === true) {
        var t;
        t = this.config.show_loading = document.createElement("DIV");
        t.className = 'dhx_loading';
        t.style.left = Math.round((this._x - 128) / 2) + "px";
        t.style.top = Math.round((this._y - 15) / 2) + "px";
        this._obj.appendChild(t);
    }
});
scheduler.attachEvent("onXLE", function () {
    var t = this.config.show_loading;
    if (t && typeof t == "object") {
        this._obj.removeChild(t);
        this.config.show_loading = true;
    }
});

/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in not GPL project. Please contact sales@dhtmlx.com for details
*/
scheduler.ical = {
    parse: function (str) {
        var data = str.match(RegExp(this.c_start + "[^\f]*" + this.c_end, ""));
        if (!data.length) return;

        //unfolding 
        data[0] = data[0].replace(/[\r\n]+(?=[a-z \t])/g, " ");
        //drop property
        data[0] = data[0].replace(/\;[^:\r\n]*:/g, ":");


        var incoming = [];
        var match;
        var event_r = RegExp("(?:" + this.e_start + ")([^\f]*?)(?:" + this.e_end + ")", "g");
        while ((match = event_r.exec(data)) !== null) {
            var e = {};
            var param;
            var param_r = /[^\r\n]+[\r\n]+/g;
            while ((param = param_r.exec(match[1])) !== null)
                this.parse_param(param.toString(), e);
            if (e.uid && !e.id) e.id = e.uid; //fallback to UID, when ID is not defined
            incoming.push(e);
        }
        return incoming;
    },
    parse_param: function (str, obj) {
        var d = str.indexOf(":");
        if (d == -1) return;

        var name = str.substr(0, d).toLowerCase();
        var value = str.substr(d + 1).replace(/\\\,/g, ",").replace(/[\r\n]+$/, "");
        if (name == "summary")
            name = "text";
        else if (name == "dtstart") {
            name = "start_date";
            value = this.parse_date(value, 0, 0);
        }
        else if (name == "dtend") {
            name = "end_date";
            value = this.parse_date(value, 0, 0);
        }
        obj[name] = value;
    },
    parse_date: function (value, dh, dm) {
        var t = value.split("T");
        if (t[1]) {
            dh = t[1].substr(0, 2);
            dm = t[1].substr(2, 2);
        }
        var dy = t[0].substr(0, 4);
        var dn = parseInt(t[0].substr(4, 2), 10) - 1;
        var dd = t[0].substr(6, 2);
        if (scheduler.config.server_utc && !t[1]) { // if no hours/minutes were specified == full day event
            return new Date(Date.UTC(dy, dn, dd, dh, dm));
        }
        return new Date(dy, dn, dd, dh, dm);
    },
    c_start: "BEGIN:VCALENDAR",
    e_start: "BEGIN:VEVENT",
    e_end: "END:VEVENT",
    c_end: "END:VCALENDAR"
};
scheduler._lightbox_controls = {};
scheduler.formSection = function (name) {
    var config = this.config.lightbox.sections;
    var i = 0;
    for (i; i < config.length; i++)
        if (config[i].name == name)
            break;
    var section = config[i];
    if (!scheduler._lightbox)
        scheduler.getLightbox();
    var header = document.getElementById(section.id);
    var node = header.nextSibling;

    var result = {
        section: section,
        header: header,
        node: node,
        getValue: function (ev) {
            return scheduler.form_blocks[section.type].get_value(node, (ev || {}), section);
        },
        setValue: function (value, ev) {
            return scheduler.form_blocks[section.type].set_value(node, value, (ev || {}), section);
        }
    };

    var handler = scheduler._lightbox_controls["get_" + section.type + "_control"];
    return handler ? handler(result) : result;
};
scheduler._lightbox_controls.get_template_control = function (result) {
    result.control = result.node;
    return result;
};
scheduler._lightbox_controls.get_select_control = function (result) {
    result.control = result.node.getElementsByTagName('select')[0];
    return result;
};
scheduler._lightbox_controls.get_textarea_control = function (result) {
    result.control = result.node.getElementsByTagName('textarea')[0];
    return result;
};
scheduler._lightbox_controls.get_time_control = function (result) {
    result.control = result.node.getElementsByTagName('select'); // array
    return result;
};
scheduler.form_blocks = {
    template: {
        render: function (sns) {
            var height = (sns.height || "30") + "px";
            return "<div class='dhx_cal_ltext dhx_cal_template' style='height:" + height + ";'></div>";
        },
        set_value: function (node, value, ev, config) {
            node.innerHTML = value || "";
        },
        get_value: function (node, ev, config) {
            return node.innerHTML || "";
        },
        focus: function (node) {
        }
    },
    textarea: {
        render: function (sns) {
            var height = (sns.height || "130") + "px";
            return "<div class='dhx_cal_ltext' style='height:" + height + ";'><textarea></textarea></div>";
        },
        set_value: function (node, value, ev) {
            node.firstChild.value = value || "";
        },
        get_value: function (node, ev) {
            return node.firstChild.value;
        },
        focus: function (node) {
            var a = node.firstChild; scheduler._focus(a, true);
        }
    },
    select: {
        render: function (sns) {
            var height = (sns.height || "23") + "px";
            var html = "<div class='dhx_cal_ltext' style='height:" + height + ";'><select style='width:100%;'>";
            for (var i = 0; i < sns.options.length; i++)
                html += "<option value='" + sns.options[i].key + "'>" + sns.options[i].label + "</option>";
            html += "</select></div>";
            return html;
        },
        set_value: function (node, value, ev, sns) {
            var select = node.firstChild;
            if (!select._dhx_onchange && sns.onchange) {
                select.onchange = sns.onchange;
                select._dhx_onchange = true;
            }
            if (typeof value == "undefined")
                value = (select.options[0] || {}).value;
            select.value = value || "";
        },
        get_value: function (node, ev) {
            return node.firstChild.value;
        },
        focus: function (node) {
            var a = node.firstChild; scheduler._focus(a, true);
        }
    },
    time: {
        render: function (sns) {
            if (!sns.time_format) {
                // default order
                sns.time_format = ["%H:%i", "%d", "%m", "%Y"];
            }
            // map: default order => real one
            sns._time_format_order = {};
            var time_format = sns.time_format;

            var cfg = scheduler.config;
            var dt = this.date.date_part(scheduler._currentDate());
            var last = 24 * 60, first = 0;
            if (scheduler.config.limit_time_select) {
                last = 60 * cfg.last_hour + 1;
                first = 60 * cfg.first_hour;
                dt.setHours(cfg.first_hour);
            }
            var html = "";

            for (var p = 0; p < time_format.length; p++) {
                var time_option = time_format[p];

                // adding spaces between selects
                if (p > 0) {
                    html += " ";
                }

                switch (time_option) {
                    case "%Y":
                        sns._time_format_order[3] = p;
                        //year
                        html += "<select>";
                        var year = dt.getFullYear() - 5; //maybe take from config?
                        for (var i = 0; i < 10; i++)
                            html += "<option value='" + (year + i) + "'>" + (year + i) + "</option>";
                        html += "</select> ";
                        break;
                    case "%m":
                        sns._time_format_order[2] = p;
                        //month
                        html += "<select>";
                        for (var i = 0; i < 12; i++)
                            html += "<option value='" + i + "'>" + this.locale.date.month_full[i] + "</option>";
                        html += "</select>";
                        break;
                    case "%d":
                        sns._time_format_order[1] = p;
                        //days
                        html += "<select>";
                        for (var i = 1; i < 32; i++)
                            html += "<option value='" + i + "'>" + i + "</option>";
                        html += "</select>";
                        break;
                    case "%H:%i":
                        sns._time_format_order[0] = p;
                        //hours
                        html += "<select>";
                        var i = first;
                        var tdate = dt.getDate();
                        sns._time_values = [];

                        while (i < last) {
                            var time = this.templates.time_picker(dt);
                            html += "<option value='" + i + "'>" + time + "</option>";
                            sns._time_values.push(i);
                            dt.setTime(dt.valueOf() + this.config.time_step * 60 * 1000);
                            var diff = (dt.getDate() != tdate) ? 1 : 0; // moved or not to the next day
                            i = diff * 24 * 60 + dt.getHours() * 60 + dt.getMinutes();
                        }
                        html += "</select>";
                        break;
                }
            }

            return "<div style='height:30px;padding-top:0px;font-size:inherit;' class='dhx_section_time'>" + html + "<span style='font-weight:normal; font-size:10pt;'> &nbsp;&ndash;&nbsp; </span>" + html + "</div>";
        },
        set_value: function (node, value, ev, config) {
            var cfg = scheduler.config;
            var s = node.getElementsByTagName("select");
            var map = config._time_format_order;
            var start_date, end_date;

            if (cfg.full_day) {
                if (!node._full_day) {
                    var html = "<label class='dhx_fullday'><input type='checkbox' name='full_day' value='true'> " + scheduler.locale.labels.full_day + "&nbsp;</label></input>";
                    if (!scheduler.config.wide_form)
                        html = node.previousSibling.innerHTML + html;
                    node.previousSibling.innerHTML = html;
                    node._full_day = true;
                }
                var input = node.previousSibling.getElementsByTagName("input")[0];
                input.checked = (scheduler.date.time_part(ev.start_date) === 0 && scheduler.date.time_part(ev.end_date) === 0);

                s[map[0]].disabled = input.checked;
                s[map[0] + s.length / 2].disabled = input.checked;

                input.onclick = function () {
                    if (input.checked) {
                        var obj = {};
                        scheduler.form_blocks.time.get_value(node, obj, config);

                        start_date = scheduler.date.date_part(obj.start_date);
                        end_date = scheduler.date.date_part(obj.end_date);

                        if (+end_date == +start_date || (+end_date >= +start_date && (ev.end_date.getHours() !== 0 || ev.end_date.getMinutes() !== 0)))
                            end_date = scheduler.date.add(end_date, 1, "day");
                    } else {
                        start_date = null;
                        end_date = null;
                    }

                    s[map[0]].disabled = input.checked;
                    s[map[0] + s.length / 2].disabled = input.checked;

                    _fill_lightbox_select(s, 0, start_date || ev.start_date);
                    _fill_lightbox_select(s, 4, end_date || ev.end_date);
                };
            }

            if (cfg.auto_end_date && cfg.event_duration) {
                var _update_lightbox_select = function () {
                    start_date = new Date(s[map[3]].value, s[map[2]].value, s[map[1]].value, 0, s[map[0]].value);
                    end_date = new Date(start_date.getTime() + (scheduler.config.event_duration * 60 * 1000));
                    _fill_lightbox_select(s, 4, end_date);
                };
                for (var i = 0; i < 4; i++) {
                    s[i].onchange = _update_lightbox_select;
                }
            }

            function _fill_lightbox_select(s, i, d) {
                var time_values = config._time_values;
                var direct_value = d.getHours() * 60 + d.getMinutes();
                var fixed_value = direct_value;
                var value_found = false;
                for (var k = 0; k < time_values.length; k++) {
                    var t_v = time_values[k];
                    if (t_v === direct_value) {
                        value_found = true;
                        break;
                    }
                    if (t_v < direct_value)
                        fixed_value = t_v;
                }

                s[i + map[0]].value = (value_found) ? direct_value : fixed_value;
                if (!(value_found || fixed_value)) {
                    s[i + map[0]].selectedIndex = -1;//show empty select in FF
                }
                s[i + map[1]].value = d.getDate();
                s[i + map[2]].value = d.getMonth();
                s[i + map[3]].value = d.getFullYear();
            }

            _fill_lightbox_select(s, 0, ev.start_date);
            _fill_lightbox_select(s, 4, ev.end_date);
        },
        get_value: function (node, ev, config) {
            var s = node.getElementsByTagName("select");
            var map = config._time_format_order;

            ev.start_date = new Date(s[map[3]].value, s[map[2]].value, s[map[1]].value, 0, s[map[0]].value);
            ev.end_date = new Date(s[map[3] + 4].value, s[map[2] + 4].value, s[map[1] + 4].value, 0, s[map[0] + 4].value);

            if (!(s[map[3]].value && s[map[3] + 4].value)) {
                // use the previous date if start/end years are empty (outside lightbox range)
                var original = this.getEvent(this._lightbox_id);
                if (original) {
                    ev.start_date = original.start_date;
                    ev.end_date = original.end_date;
                }
            }

            if (ev.end_date <= ev.start_date)
                ev.end_date = scheduler.date.add(ev.start_date, scheduler.config.time_step, "minute");
            return {
                start_date: new Date(ev.start_date),
                end_date: new Date(ev.end_date)
            };
        },
        focus: function (node) {
            scheduler._focus(node.getElementsByTagName("select")[0]);
        }
    }
};
scheduler.showCover = function (box) {
    if (box) {
        box.style.display = "block";

        var scroll_top = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
        var scroll_left = window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft;

        var view_height = window.innerHeight || document.documentElement.clientHeight;

        if (scroll_top) // if vertical scroll on window
            box.style.top = Math.round(scroll_top + Math.max((view_height - box.offsetHeight) / 2, 0)) + "px";
        else // vertical scroll on body
            box.style.top = Math.round(Math.max(((view_height - box.offsetHeight) / 2), 0) + 9) + "px"; // +9 for compatibility with auto tests

        // not quite accurate but used for compatibility reasons
        if (document.documentElement.scrollWidth > document.body.offsetWidth) // if horizontal scroll on the window
            box.style.left = Math.round(scroll_left + (document.body.offsetWidth - box.offsetWidth) / 2) + "px";
        else // horizontal scroll on the body
            box.style.left = Math.round((document.body.offsetWidth - box.offsetWidth) / 2) + "px";
    }
    this.show_cover();
};
scheduler.showLightbox = function (id) {
    if (!id) return;
    if (!this.callEvent("onBeforeLightbox", [id])) {
        if (this._new_event)
            this._new_event = null;
        return;
    }
    var box = this.getLightbox();
    //this.showCover(box);
    this._fill_lightbox(id, box);
    this.callEvent("onLightbox", [id]);

    this.callEvent('onShowLightbox', [id]);
};
scheduler._fill_lightbox = function (id, box) {
    //var ev = this.getEvent(id);
    //var s = box.getElementsByTagName("span");
    //if (scheduler.templates.lightbox_header) {
    //    s[1].innerHTML = "";
    //    s[2].innerHTML = scheduler.templates.lightbox_header(ev.start_date, ev.end_date, ev);
    //} else {
    //    s[1].innerHTML = this.templates.event_header(ev.start_date, ev.end_date, ev);
    //    s[2].innerHTML = (this.templates.event_bar_text(ev.start_date, ev.end_date, ev) || "").substr(0, 70); //IE6 fix
    //}

    //var sns = this.config.lightbox.sections;
    //for (var i = 0; i < sns.length; i++) {
    //    var current_sns = sns[i];
    //    var node = document.getElementById(current_sns.id).nextSibling;
    //    var block = this.form_blocks[current_sns.type];
    //    var value = (ev[current_sns.map_to] !== undefined) ? ev[current_sns.map_to] : current_sns.default_value;
    //    block.set_value.call(this, node, value, ev, current_sns);
    //    if (sns[i].focus)
    //        block.focus.call(this, node);
    //}

    scheduler._lightbox_id = id;
};
scheduler._lightbox_out = function (ev) {
    var sns = this.config.lightbox.sections;
    for (var i = 0; i < sns.length; i++) {
        var node = document.getElementById(sns[i].id);
        node = (node ? node.nextSibling : node);
        var block = this.form_blocks[sns[i].type];
        var res = block.get_value.call(this, node, ev, sns[i]);
        if (sns[i].map_to != "auto")
            ev[sns[i].map_to] = res;
    }
    return ev;
};
scheduler._empty_lightbox = function (data) {
    var id = scheduler._lightbox_id;
    var ev = this.getEvent(id);
    var box = this.getLightbox();

    this._lame_copy(ev, data);

    this.setEvent(ev.id, ev);
    this._edit_stop_event(ev, true);
    this.render_view_data();
};
scheduler.hide_lightbox = function (id) {
    this.hideCover(this.getLightbox());
    this._lightbox_id = null;
    this.callEvent("onAfterLightbox", []);
};
scheduler.hideCover = function (box) {
    if (box) box.style.display = "none";
    this.hide_cover();
};
scheduler.hide_cover = function () {
    if (this._cover)
        this._cover.parentNode.removeChild(this._cover);
    this._cover = null;
};
scheduler.show_cover = function () {
    if (this._cover)
        return;

    this._cover = document.createElement("DIV");
    this._cover.className = "dhx_cal_cover";
    var _document_height = ((document.height !== undefined) ? document.height : document.body.offsetHeight);
    var _scroll_height = ((document.documentElement) ? document.documentElement.scrollHeight : 0);
    this._cover.style.height = Math.max(_document_height, _scroll_height) + 'px';
    document.body.appendChild(this._cover);
};
scheduler.save_lightbox = function () {
    //var data = this._lightbox_out({}, this._lame_copy(this.getEvent(this._lightbox_id)));
    var data = this._lame_copy(this.getEvent(this._lightbox_id));
    if (this.checkEvent("onEventSave") && !this.callEvent("onEventSave", [this._lightbox_id, data, this._new_event]))
        return;
    this._empty_lightbox(data);
    this.hide_lightbox();
};
scheduler.startLightbox = function (id, box) {
    this._lightbox_id = id;
    this._custom_lightbox = true;

    this._temp_lightbox = this._lightbox;
    this._lightbox = box;
    this.showCover(box);
};
scheduler.endLightbox = function (mode, box) {
    this._edit_stop_event(scheduler.getEvent(this._lightbox_id), mode);
    if (mode)
        scheduler.render_view_data();
    this.hideCover(box);

    if (this._custom_lightbox) {
        this._lightbox = this._temp_lightbox;
        this._custom_lightbox = false;
    }
    this._temp_lightbox = this._lightbox_id = null; // in case of custom lightbox user only calls endLightbox so we need to reset _lightbox_id
};
scheduler.resetLightbox = function () {
    if (scheduler._lightbox && !scheduler._custom_lightbox)
        scheduler._lightbox.parentNode.removeChild(scheduler._lightbox);
    scheduler._lightbox = null;
};
scheduler.cancel_lightbox = function () {
    this.callEvent("onEventCancel", [this._lightbox_id, this._new_event]);
    this.endLightbox(false);
    this.hide_lightbox();
};
scheduler._init_lightbox_events = function () {
    this.getLightbox().onclick = function (e) {
        var src = e ? e.target : event.srcElement;
        if (!src.className) src = src.previousSibling;
        if (src && src.className)
            switch (src.className) {
                case "dhx_save_btn":
                    scheduler.save_lightbox();
                    break;
                case "dhx_delete_btn":
                    var c = scheduler.locale.labels.confirm_deleting;

                    scheduler._dhtmlx_confirm(c, scheduler.locale.labels.title_confirm_deleting, function () {
                        scheduler.deleteEvent(scheduler._lightbox_id);
                        scheduler._new_event = null; //clear flag, if it was unsaved event
                        scheduler.hide_lightbox();
                    });

                    break;
                case "dhx_cancel_btn":
                    scheduler.cancel_lightbox();
                    break;

                default:
                    if (src.getAttribute("dhx_button")) {
                        scheduler.callEvent("onLightboxButton", [src.className, src, e]);
                    } else {
                        var index, block, sec;
                        if (src.className.indexOf("dhx_custom_button") != -1) {
                            if (src.className.indexOf("dhx_custom_button_") != -1) {
                                index = src.parentNode.getAttribute("index");
                                sec = src.parentNode.parentNode;
                            } else {
                                index = src.getAttribute("index");
                                sec = src.parentNode;
                                src = src.firstChild;
                            }
                        }
                        if (index) {
                            block = scheduler.form_blocks[scheduler.config.lightbox.sections[index].type];
                            block.button_click(index, src, sec, sec.nextSibling);
                        }
                    }
                    break;
            }
    };
    this.getLightbox().onkeydown = function (e) {
        switch ((e || event).keyCode) {
            case scheduler.keys.edit_save:
                if ((e || event).shiftKey) return;
                scheduler.save_lightbox();
                break;
            case scheduler.keys.edit_cancel:
                scheduler.cancel_lightbox();
                break;
            default:
                break;
        }
    };
};
scheduler.setLightboxSize = function () {
    var d = this._lightbox;
    if (!d) return;

    var con = d.childNodes[1];
    con.style.height = "0px";
    con.style.height = con.scrollHeight + "px";
    d.style.height = con.scrollHeight + scheduler.xy.lightbox_additional_height + "px";
    con.style.height = con.scrollHeight + "px"; //it is incredible , how ugly IE can be
};

scheduler._init_dnd_events = function () {
    dhtmlxEvent(document.body, "mousemove", scheduler._move_while_dnd);
    dhtmlxEvent(document.body, "mouseup", scheduler._finish_dnd);
    scheduler._init_dnd_events = function () { };
};
scheduler._move_while_dnd = function (e) {
    if (scheduler._dnd_start_lb) {
        if (!document.dhx_unselectable) {
            document.body.className += " dhx_unselectable";
            document.dhx_unselectable = true;
        }
        var lb = scheduler.getLightbox();
        var now = (e && e.target) ? [e.pageX, e.pageY] : [event.clientX, event.clientY];
        lb.style.top = scheduler._lb_start[1] + now[1] - scheduler._dnd_start_lb[1] + "px";
        lb.style.left = scheduler._lb_start[0] + now[0] - scheduler._dnd_start_lb[0] + "px";
    }
};
scheduler._ready_to_dnd = function (e) {
    var lb = scheduler.getLightbox();
    scheduler._lb_start = [parseInt(lb.style.left, 10), parseInt(lb.style.top, 10)];
    scheduler._dnd_start_lb = (e && e.target) ? [e.pageX, e.pageY] : [event.clientX, event.clientY];
};
scheduler._finish_dnd = function () {
    if (scheduler._lb_start) {
        scheduler._lb_start = scheduler._dnd_start_lb = false;
        document.body.className = document.body.className.replace(" dhx_unselectable", "");
        document.dhx_unselectable = false;
    }
};
scheduler.getLightbox = function () { //scheduler.config.wide_form=true;
    if (!this._lightbox) {
        var d = document.createElement("DIV");
        d.className = "dhx_cal_light";
        if (scheduler.config.wide_form)
            d.className += " dhx_cal_light_wide";
        if (scheduler.form_blocks.recurring)
            d.className += " dhx_cal_light_rec";

        if (/msie|MSIE 6/.test(navigator.userAgent))
            d.className += " dhx_ie6";
        d.style.visibility = "hidden";
        var html = this._lightbox_template;

        var buttons = this.config.buttons_left;
        for (var i = 0; i < buttons.length; i++)
            html += "<div class='dhx_btn_set dhx_left_btn_set " + buttons[i] + "_set'><div dhx_button='1' class='" + buttons[i] + "'></div><div>" + scheduler.locale.labels[buttons[i]] + "</div></div>";

        buttons = this.config.buttons_right;
        for (var i = 0; i < buttons.length; i++)
            html += "<div class='dhx_btn_set dhx_right_btn_set " + buttons[i] + "_set' style='float:right;'><div dhx_button='1' class='" + buttons[i] + "'></div><div>" + scheduler.locale.labels[buttons[i]] + "</div></div>";

        html += "</div>";
        d.innerHTML = html;
        if (scheduler.config.drag_lightbox) {
            d.firstChild.onmousedown = scheduler._ready_to_dnd;
            d.firstChild.onselectstart = function () { return false; };
            d.firstChild.style.cursor = "pointer";
            scheduler._init_dnd_events();

        }
        document.body.insertBefore(d, document.body.firstChild);
        this._lightbox = d;

        var sns = this.config.lightbox.sections;
        html = "";
        for (var i = 0; i < sns.length; i++) {
            var block = this.form_blocks[sns[i].type];
            if (!block) continue; //ignore incorrect blocks
            sns[i].id = "area_" + this.uid();
            var button = "";
            if (sns[i].button) {
                button = "<div class='dhx_custom_button' index='" + i + "'><div class='dhx_custom_button_" + sns[i].button + "'></div><div>" + this.locale.labels["button_" + sns[i].button] + "</div></div>";
            }

            if (this.config.wide_form) {
                html += "<div class='dhx_wrap_section'>";
            }

            var label_name = this.locale.labels["section_" + sns[i].name];
            if (typeof label_name !== "string") {
                label_name = sns[i].name;
            }
            html += "<div id='" + sns[i].id + "' class='dhx_cal_lsection'>" + button + label_name + "</div>" + block.render.call(this, sns[i]);
            html += "</div>";
        }

        var ds = d.getElementsByTagName("div");
        for (var i = 0; i < ds.length; i++) {
            var t_ds = ds[i];
            if (t_ds.className == "dhx_cal_larea") {
                t_ds.innerHTML = html;
                break;
            }
        }

        //sizes
        this.setLightboxSize();

        this._init_lightbox_events(this);
        d.style.display = "none";
        d.style.visibility = "visible";
    }
    return this._lightbox;
};

scheduler.attachEvent("onEventIdChange", function (old_id, new_id) {
    if (this._lightbox_id == old_id)
        this._lightbox_id = new_id;
});

scheduler._lightbox_template = "<div class='dhx_cal_ltitle'><span class='dhx_mark'>&nbsp;</span><span class='dhx_time'></span><span class='dhx_title'></span></div><div class='dhx_cal_larea'></div>";

scheduler._init_touch_events = function () {
    if (this.config.touch != "force")
        this.config.touch = this.config.touch &&
            ((navigator.userAgent.indexOf("Mobile") != -1) ||
                (navigator.userAgent.indexOf("iPad") != -1) ||
                (navigator.userAgent.indexOf("Android") != -1) ||
                (navigator.userAgent.indexOf("Touch") != -1));

    if (this.config.touch) {
        this.xy.scroll_width = 0;
        if (window.navigator.msPointerEnabled) {
            this._touch_events(["MSPointerMove", "MSPointerDown", "MSPointerUp"], function (ev) {
                if (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE) return null;
                return ev;
            }, function (ev) {
                return (!ev || ev.pointerType == ev.MSPOINTER_TYPE_MOUSE);
            });
            this._obj.ondblclick = function () { };
        } else
            this._touch_events(["touchmove", "touchstart", "touchend"], function (ev) {
                if (ev.touches && ev.touches.length > 1) return null;
                if (ev.touches[0])
                    return { target: ev.target, pageX: ev.touches[0].pageX, pageY: ev.touches[0].pageY };
                else
                    return ev;
            }, function () { return false; });
    }
};

scheduler._touch_events = function (names, accessor, ignore) {
    //webkit on android need to be handled separately
    var a_webkit = (navigator.userAgent.indexOf("Android") != -1) && (navigator.userAgent.indexOf("WebKit") != -1);
    var source, tracker, timer, drag_mode, scroll_mode, action_mode;
    var dblclicktime = 0;

    function attachTouchEvent(element, name, callback) {
        //touch gestures must be disabled when ligthbox is opened
        dhtmlxEvent(element, name, function (e) {
            if (scheduler._is_lightbox_open()) {
                return true;
            } else {
                return callback(e);
            }
        });
    }

    function check_direction_swipe(s_ev, e_ev, step) {
        if (!s_ev || !e_ev) return;

        var dy = Math.abs(s_ev.pageY - e_ev.pageY);
        var dx = Math.abs(s_ev.pageX - e_ev.pageX);
        if (dx > step && (!dy || (dx / dy > 3))) {
            if (s_ev.pageX > e_ev.pageX)
                scheduler._click.dhx_cal_next_button();
            else
                scheduler._click.dhx_cal_prev_button();
        }
    }

    function doMouseMove(e) {
        var dnd = scheduler.getState().drag_mode,
            timeline = scheduler.matrix ? scheduler.matrix[scheduler._mode] : false;

        var original_render = scheduler.render_view_data;
        if (dnd == 'create' && timeline) {
            //suppress full redraw of timeline on creating event
            scheduler.render_view_data = function () {
                var id = scheduler.getState().drag_id;
                var ev = scheduler.getEvent(id);
                var property = timeline.y_property;

                var evs = scheduler.getEvents(ev.start_date, ev.end_date);
                for (var i = 0; i < evs.length; i++) {
                    if (evs[i][property] != ev[property]) {
                        evs.splice(i, 1);
                        i--;
                    }
                }
                ev._sorder = evs.length - 1;
                ev._count = evs.length;

                this.render_data([ev], scheduler.getState().mode);

            };
        }

        scheduler._on_mouse_move(e);

        if (dnd == 'create' && timeline) {
            scheduler.render_view_data = original_render;
        }
    }
    attachTouchEvent(document.body, names[0], function (e) {
        if (ignore(e)) return;

        if (drag_mode) {
            doMouseMove(accessor(e));
            scheduler._update_global_tip();
            if (e.preventDefault)
                e.preventDefault();
            e.cancelBubble = true;
            return false;
        }

        if (tracker && a_webkit) {
            check_direction_swipe(tracker, accessor(e), 0);
        }

        tracker = accessor(e);
        //ignore common and scrolling moves
        if (!action_mode) return;

        //multitouch		
        if (!tracker) {
            scroll_mode = true;
            return;
        }

        //target changed - probably in scroll mode

        if (source.target != tracker.target || (Math.abs(source.pageX - tracker.pageX) > 5) || (Math.abs(source.pageY - tracker.pageY) > 5)) {
            scroll_mode = true;
            clearTimeout(timer);
        }

    });

    attachTouchEvent(this._els["dhx_cal_data"][0], "scroll", drag_cancel);
    attachTouchEvent(this._els["dhx_cal_data"][0], "touchcancel", drag_cancel);
    attachTouchEvent(this._els["dhx_cal_data"][0], "contextmenu", function (e) {
        if (action_mode) {
            if (e && e.preventDefault)
                e.preventDefault();
            (e || event).cancelBubble = true;
            return false;
        }
    });
    attachTouchEvent(this._els["dhx_cal_data"][0], names[1], function (e) {
        if (ignore(e)) return;

        var fake_event;
        drag_mode = scroll_mode = false;
        action_mode = true;
        scheduler._temp_touch_block = true;
        fake_event = tracker = accessor(e);

        if (!fake_event) {
            scroll_mode = true;
            return;
        }

        //dbl click
        var now = new Date();

        if (!scroll_mode && !drag_mode && now - dblclicktime < 250) {
            scheduler._click.dhx_cal_data(fake_event);
            window.setTimeout(function () {
                scheduler._on_dbl_click(fake_event);
            }, 50);

            if (e.preventDefault)
                e.preventDefault();
            e.cancelBubble = true;
            scheduler._block_next_stop = true;
            return false;
        }
        dblclicktime = now;

        //drag

        if (scroll_mode || drag_mode || !scheduler.config.touch_drag)
            return;

        //there is no target
        timer = setTimeout(function () {

            drag_mode = true;
            var target = source.target;
            if (target && target.className && target.className.indexOf("dhx_body") != -1)
                target = target.previousSibling;

            scheduler._on_mouse_down(source, target);
            if (scheduler._drag_mode && scheduler._drag_mode != "create") {
                var pos = -1;
                scheduler.for_rendered(scheduler._drag_id, function (node, i) {
                    pos = node.getBoundingClientRect().top;
                    node.style.display = 'none';
                    scheduler._rendered.splice(i, 1);
                });
                if (pos >= 0) {
                    var step = scheduler.config.time_step;
                    scheduler._move_pos_shift = step * Math.round((fake_event.pageY - pos) * 60 / (scheduler.config.hour_size_px * step));
                }
            }

            if (scheduler.config.touch_tip)
                scheduler._show_global_tip();
            scheduler._on_mouse_move(source);
        }, scheduler.config.touch_drag);

        source = fake_event;
    });
    function drag_cancel(e) {
        scheduler._hide_global_tip();
        if (drag_mode) {
            scheduler._on_mouse_up(accessor(e || event));
            scheduler._temp_touch_block = false;
        }
        scheduler._drag_id = null;
        scheduler._drag_mode = null;
        scheduler._drag_pos = null;

        clearTimeout(timer);
        drag_mode = action_mode = false;
        scroll_mode = true;
    }
    attachTouchEvent(this._els["dhx_cal_data"][0], names[2], function (e) {
        if (ignore(e)) return;

        if (!drag_mode)
            check_direction_swipe(source, tracker, 200);

        if (drag_mode)
            scheduler._ignore_next_click = true;

        drag_cancel(e);
        if (scheduler._block_next_stop) {
            scheduler._block_next_stop = false;
            if (e.preventDefault)
                e.preventDefault();
            e.cancelBubble = true;
            return false;
        }
    });

    dhtmlxEvent(document.body, names[2], drag_cancel);
};

scheduler._show_global_tip = function () {
    scheduler._hide_global_tip();

    var toptip = scheduler._global_tip = document.createElement("DIV");
    toptip.className = 'dhx_global_tip';

    scheduler._update_global_tip(1);

    document.body.appendChild(toptip);
};
scheduler._update_global_tip = function (init) {
    var toptip = scheduler._global_tip;
    if (toptip) {
        var time = "";
        if (scheduler._drag_id && !init) {
            var ev = scheduler.getEvent(scheduler._drag_id);
            if (ev)
                time = "<div>" + (ev._timed ? scheduler.templates.event_header(ev.start_date, ev.end_date, ev) : scheduler.templates.day_date(ev.start_date, ev.end_date, ev)) + "</div>";
        }

        if (scheduler._drag_mode == "create" || scheduler._drag_mode == "new-size")
            toptip.innerHTML = (scheduler.locale.drag_to_create || "Drag to create") + time;
        else
            toptip.innerHTML = (scheduler.locale.drag_to_move || "Drag to move") + time;
    }
};
scheduler._hide_global_tip = function () {
    var toptip = scheduler._global_tip;
    if (toptip && toptip.parentNode) {
        toptip.parentNode.removeChild(toptip);
        scheduler._global_tip = 0;
    }
};

scheduler._validId = function (id) {
    return true;
};

scheduler.setUserData = function (id, name, value) {
    if (id)
        this.getEvent(id)[name] = value;
    else
        this._userdata[name] = value;
};
scheduler.getUserData = function (id, name) {
    return id ? this.getEvent(id)[name] : this._userdata[name];
};
scheduler._set_event_text_style = function (id, style) {
    this.for_rendered(id, function (r) {
        r.style.cssText += ";" + style;
    });
    var ev = this.getEvent(id);
    ev["_text_style"] = style;
    this.event_updated(ev);
};

scheduler._update_callback = function (upd, id) {
    var data = scheduler._xmlNodeToJSON(upd.firstChild);
    data.text = data.text || data._tagvalue;
    data.start_date = scheduler.templates.xml_date(data.start_date);
    data.end_date = scheduler.templates.xml_date(data.end_date);

    scheduler.addEvent(data);
};
scheduler._skin_settings = {
    fix_tab_position: [1, 0],
    use_select_menu_space: [1, 0],
    wide_form: [1, 0],

    //hour_size_px: [60, 45],
    hour_size_px: [(60 / scheduler.config.time_slot_unit) * scheduler.config.time_slot_height, 45],
    displayed_event_color: ["#ff4a4a", "ffc5ab"],
    displayed_event_text_color: ["#ffef80", "7e2727"]
};

scheduler._skin_xy = {
    lightbox_additional_height: [90, 50],
    nav_height: [59, 22],
    bar_height: [24, 20]
};

scheduler._configure = function (col, data, skin) {
    for (var key in data)
        if (typeof col[key] == "undefined")
            col[key] = data[key][skin];
};
scheduler._skin_init = function () {
    if (!scheduler.skin) {
        var links = document.getElementsByTagName("link");
        for (var i = 0; i < links.length; i++) {
            var res = links[i].href.match("dhtmlxscheduler_([a-z]+).css");
            if (res) {
                scheduler.skin = res[1];
                break;
            }
        }
    }

    var set = 0;
    if (scheduler.skin && (scheduler.skin === "classic" || scheduler.skin === "glossy")) set = 1;

    //apply skin related settings
    this._configure(scheduler.config, scheduler._skin_settings, set);
    this._configure(scheduler.xy, scheduler._skin_xy, set);

    if (scheduler.skin === "flat") {
        scheduler.xy.scale_height = 35;
        scheduler.templates.hour_scale = function (date) {
            var min = date.getMinutes();
            min = min < 10 ? "0" + min : min;
            var html = "<span class='dhx_scale_h'>" + date.getHours() + "</span>" +
                "<span class='dhx_scale_m'>&nbsp;" + min + "</span>";
            return html;
        };
    }

    //classic skin need not any further customization
    if (set) return;

    var minic = scheduler.config.minicalendar;
    if (minic) minic.padding = 14;

    scheduler.templates.event_bar_date = function (start, end, ev) {
        return "• <b>" + scheduler.templates.event_date(start) + "</b> ";
    };

    //scheduler._lightbox_template="<div class='dhx_cal_ltitle'><span class='dhx_mark'>&nbsp;</span><span class='dhx_time'></span><span class='dhx_title'></span><div class='dhx_close_icon'></div></div><div class='dhx_cal_larea'></div>";
    scheduler.attachEvent("onTemplatesReady", function () {

        var date_to_str = scheduler.date.date_to_str("%d");
        if (!scheduler.templates._old_month_day) {
            scheduler.templates._old_month_day = scheduler.templates.month_day;
        }
        var old_month_day = scheduler.templates._old_month_day;
        scheduler.templates.month_day = function (date) {
            if (this._mode == "month") {
                var label = date_to_str(date);
                if (date.getDate() == 1) {
                    label = scheduler.locale.date.month_full[date.getMonth()] + " " + label;
                }
                if (+date == +scheduler.date.date_part(new Date())) {
                    label = scheduler.locale.labels.dhx_cal_today_button + " " + label;
                }
                return label;
            } else {
                return old_month_day.call(this, date);
            }
        };


        if (scheduler.config.fix_tab_position) {
            var navline_divs = scheduler._els["dhx_cal_navline"][0].getElementsByTagName('div');
            var minical = null;
            var tabs = [];
            var last = 211;
            for (var i = 0; i < navline_divs.length; i++) {
                var div = navline_divs[i];
                var name = div.getAttribute("name");
                if (name) { // mode tab
                    div.style.right = "auto";
                    switch (name) {
                        case "day_tab":
                            div.style.left = "14px";
                            div.className += " dhx_cal_tab_first";
                            break;
                        case "week_tab":
                            div.style.left = "75px";
                            break;
                        case "month_tab":
                            div.style.left = "136px";
                            div.className += " dhx_cal_tab_last";
                            break;
                        default:
                            div.style.left = last + "px";
                            div.className += " dhx_cal_tab_standalone";
                            last = last + 14 + div.offsetWidth;
                            break;
                    }
                } else {
                    if ((div.className || "").indexOf("dhx_minical_icon") === 0 &&
                        div.parentNode == scheduler._els["dhx_cal_navline"][0]) {
                        // if default minicalendar icon
                        minical = div;
                    }
                }

            }

            if (minical) {
                minical.style.left = last + "px";
            }
        }

    });
    scheduler._skin_init = function () { };
};

// Added by Mason Start
scheduler.resetConfiguration = function () {
    scheduler.locale.labels.unit_tab = 'Unit';
    scheduler.locale.labels.section_custom = 'Section';
    scheduler.config.details_on_create = true;
    scheduler.config.details_on_dblclick = true;
    scheduler.config.drag_create = true;
    scheduler.config.dblclick_create = true;
    scheduler.config.xml_date = '%Y-%m-%d %H:%i';
    scheduler.config.isLiteScheduler = false;

    //scheduler.resetTimeSlotHeight();
    //scheduler._skin_xy.nav_height = [59, 22];
    //scheduler._skin_xy.bar_height = [24, 20];
    //scheduler._configure(scheduler.xy, scheduler._skin_xy, 0);
};

scheduler.resetTimeSlotHeight = function (columnHeight) {
    var startHour = this.config.first_hour;
    var lastHour = this.config.last_hour;
    var visibleHours = Math.max(lastHour - startHour + 1, 1);
    var timeSlotsPerHour = 60 / scheduler.config.time_slot_unit;
    var timeSlotHeight = Math.floor(columnHeight / (visibleHours * timeSlotsPerHour));
    //timeSlotHeight = Math.ceil(timeSlotHeight / 5) * 5;
    timeSlotHeight = Math.min(Math.max(timeSlotHeight, scheduler.config.min_time_slot_height), 50);
    // TODO: Calculate time slot height
    scheduler.xy.scale_height = timeSlotHeight;
    scheduler.config.time_slot_height = timeSlotHeight;
    scheduler.config.hour_size_px = timeSlotsPerHour * timeSlotHeight;
    scheduler.config.minute_size_px = scheduler.config.hour_size_px / 60;
};

scheduler.renderNonWorkingTimeMask = function (providerWorkingTime, columnElement) {
    // 如果没有定义工作时间，则不处理
    if (!providerWorkingTime) {
        return;
    }
    var start = scheduler.config.first_hour * 60;
    var end = scheduler.config.last_hour * 60;
    var visibleTimeRange = new Ares.TimeRange(start, end);
    var nonWorkingTimeRanges = [];
    var workingTimeRanges = [];
    if (providerWorkingTime && providerWorkingTime.workingTimeRanges
        && providerWorkingTime.workingTimeRanges.length > 0) {
        angular.forEach(providerWorkingTime.workingTimeRanges, function (workingTime) {
            var workingTimeRange = null;
            //if (workingTime.status == '休息' || workingTime.startTime == workingTime.endTime) {
            if (workingTime.startTime == workingTime.endTime) {
                workingTimeRange = _.clone(visibleTimeRange);
            } else {
                if (!workingTime.startTimeSpan) {
                    workingTime.startTimeSpan = Ares.parseTimeSpan(workingTime.startTime);
                    workingTime.endTimeSpan = Ares.parseTimeSpan(workingTime.endTime);
                }
                var startTime = Math.max(start, workingTime.startTimeSpan.totalMinutes());
                var endTime = Math.min(end, workingTime.endTimeSpan.totalMinutes());
                workingTimeRange = new Ares.TimeRange(startTime, endTime);
            }
            if (workingTimeRange) {
                workingTimeRange.color = workingTime.color;
                workingTimeRange.status = workingTime.status;
                workingTimeRange.officeId = workingTime.officeId;
                workingTimeRange.isOnDuty = workingTime.isOnDuty;
                workingTimeRanges.push(workingTimeRange);
            }
        });
        nonWorkingTimeRanges = visibleTimeRange.exclude(workingTimeRanges);
    } else {
        nonWorkingTimeRanges.push(visibleTimeRange);
    }
    var timeRanges = nonWorkingTimeRanges.concat(workingTimeRanges);
    timeRanges = _.sortBy(timeRanges, 'startTime');

    var lastEndTime = scheduler.config.first_hour * 60;
    var nonWorkingTimeMask = '';
    var workingTimeMask = '';
    angular.forEach(timeRanges, function (timeRange) {
        var top = ((timeRange.startTime - lastEndTime) / scheduler.config.time_slot_unit) * scheduler.config.time_slot_height;
        var height = ((timeRange.endTime - timeRange.startTime) / scheduler.config.time_slot_unit) * scheduler.config.time_slot_height;
        lastEndTime = timeRange.endTime;
        var bgColor = timeRange.color || 'lightgray';

        var maskClass = 'working-time-mask';
        var maskContent = '';
        if (timeRange.officeId && scheduler.getOfficeName) {
            var officeName = scheduler.getOfficeName(timeRange.officeId);
            if (officeName && officeName != '') {
                maskClass += ' working-time-mask-other-office';
                maskContent += '<div class="working-time-mask-office">' + officeName + '</div>'
                bgColor = null;
            }
        }
        if (maskContent == '' && timeRange.status && timeRange.status != '') {
            maskContent += '<div class="working-time-mask-title" style="cursor: default;">' + timeRange.status + '</div>';
        }
        bgColor = (bgColor == 'white' || bgColor == '#ffffff' || bgColor == '#FFFFFF') ? 'transparent' : bgColor;
        bgColor = bgColor ? ('background-color:' + bgColor) : '';
        var style = 'margin-top:' + top + 'px;height:' + height + 'px;' + bgColor;
        //if (timeRange.status == '休息') {
        if (timeRange.isOnDuty === false) {
            style += ';opacity:1;';
        }
        nonWorkingTimeMask += '<div class="' + maskClass + '" style="' + style + '">' + maskContent + '</div>';
    });

    var $columnElement = $(columnElement);
    $columnElement.find('.working-time-mask').remove();
    $columnElement.prepend(nonWorkingTimeMask);
};

scheduler.scrollToHour = function (hour) {
    var hourOffset = hour - scheduler.config.first_hour;
    hourOffset = Math.max(hourOffset, 0);
    var offset = scheduler.config.hour_size_px * hourOffset;
    this.scrollTo(offset);
};

scheduler.backupOffset = function () {
    if (scheduler.config.isLiteScheduler) {
        return;
    }
    scheduler._aresOffset = this.$aresApptContainer.scrollTop();
};

scheduler.resetOffset = function () {
    scheduler._aresOffset = null;
    scheduler.restoreOffset();
};

scheduler.restoreOffset = function () {
    if (scheduler.config.isLiteScheduler) {
        return;
    }
    if (scheduler._aresOffset) {
        this.scrollTo(scheduler._aresOffset);
    } else if (scheduler.config.start_working_hour) {
        scheduler.scrollToHour(scheduler.config.start_working_hour);
    }
};

scheduler.scrollTo = function (yOffset) {
    if (this.$aresApptContainer) {
        this.$aresApptContainer.stop().animate({
            scrollTop: yOffset
        }, 0);
    }
};

scheduler.showDragIndicator = function (ev) {
    if (!this.dragIndicator) {
        var dragIndicator = $('<div class="appt-drag-indicator"></div>')
        if (this.dragCoords) {
            var minutes = this.dragCoords.minutes;
            var height = (minutes / scheduler.config.time_slot_unit) * scheduler.config.time_slot_height;
            dragIndicator.height(height);
        }
        this.$aresApptContainer.append(dragIndicator);
        this.dragIndicator = dragIndicator;
    }
    if (ev) {
        var offset = { top: ev.clientY, left: ev.clientX };
        if (this.dragCoords) {
            offset.top -= this.dragCoords.top;
            offset.left -= (this.dragCoords.left - this.dragCoords.width / 2);
        }
        var pos = { x: offset.left, y: offset.top };
        var coords = scheduler._mouse_coords2(pos);
        this.dragIndicator.css('left', coords.left);
        this.dragIndicator.css('top', coords.top);
        this.dragIndicator.width(coords.width);
    }
};

scheduler.hideDragIndicator = function (ev) {
    if (this.dragIndicator) {
        this.dragIndicator.remove();
        this.dragIndicator = null;
    }
};

scheduler.dropAppt = function (ev) {
    if (this.dragIndicator) {
        var offset = scheduler.dragIndicator.offset();
        var pos = { x: offset.left, y: offset.top };
        var schedulerPos = scheduler.parsePos(pos);
        scheduler.hideDragIndicator();
        var dropParam = _.clone(schedulerPos);
        var dragObj = JSON.parse(ev.dataTransfer.getData('apptObj'));
        _.extend(dropParam, dragObj);
        scheduler.callEvent("onApptDropped", [ev, dropParam]);
    }
};

scheduler.flashAppt = function (apptId, centered) {
    this.bringIntoView(apptId, centered);
    var eventId = '#event' + apptId;
    if (this.$aresApptContainer) {
        this.$aresApptContainer.find(eventId).effect("pulsate", { times: 2 });
    }
};

scheduler.bringIntoView = function (apptId, centered) {
    if (!this.isApptVisible(apptId)) {
        var event = this.getEvent(apptId);
        if (event) {
            var eventRange = this.getEventRange(event);
            var visibleRange = this.getVisibleRange();
            var yOffset = null;
            var distance = visibleRange.getMidValue() - eventRange.getMidValue();
            var offsetMinute = 0;
            if (distance > 0) {
                offsetMinute = eventRange.min;
            } else {
                offsetMinute = eventRange.max - visibleRange.length;
            }
            if (centered) {
                var halfVisibleLength = visibleRange.length * 0.5;
                offsetMinute += distance > 0 ? -halfVisibleLength : halfVisibleLength;
            }
            yOffset = this.minuteToYOffset(offsetMinute);
            if (yOffset) {
                this.scrollTo(yOffset);
            }
        }
    }
};

scheduler.isApptVisible = function (apptId) {
    var event = this.getEvent(apptId);
    var isVisible = false;
    if (event) {
        var eventRange = this.getEventRange(event);
        var visibleRange = this.getVisibleRange();
        var overlapRange = visibleRange.intersect(eventRange);
        if (overlapRange) {
            isVisible = overlapRange.length >= (eventRange.length / 2);
        }
    }
    return isVisible;
};

scheduler.getEventRange = function (event) {
    var eventRange = new Ares.Range(event.start_date.getTotalMinutes(),
        event.end_date.getTotalMinutes());
    return eventRange;
};

scheduler.getVisibleRange = function () {
    var startMinute = this.yOffsetToMinute(this.$aresApptContainer.scrollTop());
    var endMinute = startMinute + this.yOffsetToMinute(this.$aresApptContainer.height());
    return new Ares.Range(startMinute, endMinute);
};

scheduler.yOffsetToMinute = function (yOffset) {
    var minutes = this.config.first_minute + Math.floor(yOffset / this.config.minute_size_px);
    return minutes;
};

scheduler.minuteToYOffset = function (minutes) {
    var yOffset = Math.ceil((minutes - this.config.first_minute) * this.config.minute_size_px);
    return yOffset;
};

scheduler.setDragCoords = function (coords) {
    this.dragCoords = coords;
}

scheduler._mouse_coords2 = function (pos) {
    pos.x -= getAbsoluteLeft(this._obj) + (this._table_view ? 0 : this.xy.scale_width);
    pos.y -= getAbsoluteTop(this._obj) + this.xy.nav_height + (this._dy_shift || 0) + this.xy.scale_header_height - this._els["dhx_cal_data"][0].scrollTop;
    pos = this._week_indexes_from_pos(pos, true);
    var coords = { left: this._colsS[pos.x], top: 0 };
    coords.width = this._cols[pos.x] - 2;
    coords.top = pos.y * this.xy.scale_height;
    return coords;
};

scheduler.isWeekMode = function () {
    return this._mode == 'week';
};

scheduler.parsePos = function (pos) {
    pos.x -= getAbsoluteLeft(this._obj) + (this._table_view ? 0 : this.xy.scale_width);
    pos.y -= getAbsoluteTop(this._obj) + this.xy.nav_height + (this._dy_shift || 0) + this.xy.scale_header_height;
    pos.y += this._els["dhx_cal_data"][0].scrollTop;
    var thePos = this._week_indexes_from_pos(pos, true);
    var minutes = thePos.y * scheduler.config.time_slot_unit + scheduler.config.first_hour * 60;
    var time = new Date(this._min_date);
    if (this.isWeekMode()) {
        time = moment(time).add(thePos.x, 'd').toDate();
    }
    time.setMinutes(minutes);
    var schedulerPos = { time: time, colIndex: thePos.x };
    return schedulerPos;
};

scheduler.formatDayEvent = function (theEvent) {
    var startMinute = theEvent.start_date.getHours() * 60 + theEvent.start_date.getMinutes();
    var endMinute = theEvent.end_date.getHours() * 60 + theEvent.end_date.getMinutes();
    var containerId = theEvent._sday || scheduler._get_event_sday(theEvent);
    return {
        startMinute: startMinute,
        endMinute: endMinute,
        title: theEvent.text || 'unknown',
        containerId: containerId || 0
    };
};

// Added by Mason End

if (window.jQuery) {

    (function ($) {

        var methods = [];
        $.fn.dhx_scheduler = function (config) {
            if (typeof (config) === 'string') {
                if (methods[config]) {
                    return methods[config].apply(this, []);
                } else {
                    $.error('Method ' + config + ' does not exist on jQuery.dhx_scheduler');
                }
            } else {
                var views = [];
                this.each(function () {
                    if (this && this.getAttribute) {
                        if (!this.getAttribute("dhxscheduler")) {
                            for (var key in config)
                                if (key != "data")
                                    scheduler.config[key] = config[key];

                            if (!this.getElementsByTagName("div").length) {
                                // Modified by Mason Start
                                //this.innerHTML = '<div class="dhx_cal_navline"><div class="dhx_cal_prev_button">&nbsp;</div><div class="dhx_cal_next_button">&nbsp;</div><div class="dhx_cal_today_button"></div><div class="dhx_cal_date"></div><div class="dhx_cal_tab" name="day_tab" style="right:204px;"></div><div class="dhx_cal_tab" name="week_tab" style="right:140px;"></div><div class="dhx_cal_tab" name="month_tab" style="right:76px;"></div></div><div class="dhx_cal_header"></div><div class="dhx_cal_data"></div>';

                                var columnBodyStyle = ' style="background-image:url(imgs/databg-30.png)"';
                                var columnBodyHtml = '<div class="dhx_cal_data"' + columnBodyStyle + '></div>'
                                var html = '<div class="dhx_cal_navline"><div class="dhx_cal_prev_button">&nbsp;</div><div class="dhx_cal_next_button">&nbsp;</div><div class="dhx_cal_today_button"></div><div class="dhx_cal_date"></div><div class="dhx_cal_tab" name="day_tab" style="right:204px;"></div><div class="dhx_cal_tab" name="week_tab" style="right:140px;"></div><div class="dhx_cal_tab" name="month_tab" style="right:76px;"></div></div><div class="dhx_cal_header"></div>'
                                    + columnBodyHtml;
                                this.innerHTML = html;
                                // Modified by Mason End

                                this.className += " dhx_cal_container";
                            }
                            scheduler.init(this, scheduler.config.date, scheduler.config.mode);
                            if (config.data)
                                scheduler.parse(config.data);

                            views.push(scheduler);
                        }
                    }
                });

                if (views.length === 1) return views[0];
                return views;
            }
        };
    })(jQuery);

}
(function () {

    var setCurrentView = scheduler.setCurrentView,
        updateView = scheduler.updateView;
    var update_view_timer = null,
        curr_view_timer = null;

    var lazy_setCurrentView = function (date, mode) {
        var self = this;
        window.clearTimeout(curr_view_timer);
        window.clearTimeout(update_view_timer);

        updateFlags(this, date, mode);

        curr_view_timer = setTimeout(function () {

            if (!self.callEvent("onBeforeViewChange", [self._mode, self._date, mode || self._mode, date || self._date])) return;
            updateView.call(self, date, mode);
            self.callEvent("onViewChange", [self._mode, self._date]);

            window.clearTimeout(update_view_timer);
            curr_view_timer = 0;
        }, scheduler.config.delay_render);
    };
    var lazy_updateView = function (date, mode) {
        var self = this,
            ars = arguments;

        updateFlags(this, date, mode);

        window.clearTimeout(update_view_timer);
        update_view_timer = setTimeout(function () {
            if (curr_view_timer)
                return;

            updateView.apply(self, ars);
        }, scheduler.config.delay_render);
    };
    function updateFlags(scheduler, date, mode) {
        if (date)
            scheduler._date = date;
        if (mode)
            scheduler._mode = mode;

    }
    scheduler.attachEvent("onSchedulerReady", function () {
        if (scheduler.config.delay_render) {
            scheduler.setCurrentView = lazy_setCurrentView;
            scheduler.updateView = lazy_updateView;
        } else {
            scheduler.setCurrentView = setCurrentView;
            scheduler.updateView = updateView;
        }
    });

})();
;
scheduler.attachEvent("onViewChange", function () {
    if (scheduler._is_initialized()) {
        scheduler.aresRenderTimeLine();
    }
});

scheduler.aresRenderTimeLine = function () {
    var now = scheduler._currentDate();
    var nowHours = now.getHours();
    var cfg = scheduler.config;
    var isToday = now < scheduler._max_date && now > scheduler._min_date;
    var isTimeLineVisible = isToday && nowHours >= cfg.first_hour && nowHours < cfg.last_hour;
    var isNotArrived = isToday && nowHours < cfg.first_hour;
    if (isTimeLineVisible) {
        scheduler.aresCreateTimeLine();
    } else {
        scheduler.aresRemoveTimeLine();
        if (isNotArrived) {
            var nowSeconds = (nowHours * 60 + now.getMinutes()) * 60 + now.getSeconds();
            var remainSeconds = cfg.first_hour * 3600 - nowSeconds;
            scheduler.aresDelay('aresRenderTimeLine', scheduler.aresRenderTimeLine, remainSeconds * 1000);            
        }
    }
};

scheduler.aresCreateTimeLine = function () {
    var $container = $(this._els["dhx_cal_data"][0]);
    var $timeLine = $container.children('#timeLine');
    if ($timeLine.length == 0) {
        var timeLineHtml = '<div id="timeLine" class="scheduler-time-line">'
            + '<div class="pull-left"><span class="glyphicon glyphicon-menu-left"></span></div>'
            + '</div>';
        $timeLine = $(timeLineHtml);
        $timeLine.appendTo($container);
        scheduler.aresTimeLine = $timeLine;
        this.aresRefreshTimeLinePosition();
        this.aresStartRefreshTimer();

        scheduler.attachEvent('onAfterSchedulerResize', function () {
            scheduler.aresDelay('aresSchedulerResize', scheduler.aresRenderTimeLine, 100);
            return true;
        });
    }
};

scheduler.aresRemoveTimeLine = function () {
    var $container = $(this._els["dhx_cal_data"][0]);
    $container.children('#timeLine').remove();
    this.aresTimeLine = null;
    this.aresStopRefreshTimer();
};

scheduler.aresStartRefreshTimer = function () {
    this.aresStopRefreshTimer();
    scheduler.aresTimeLineRefreshTimer = window.setInterval(function () {
        scheduler.aresRefreshTimeLinePosition();
    }, 60000);
};

scheduler.aresStopRefreshTimer = function () {
    if (scheduler.aresTimeLineRefreshTimer) {
        //alert('aresStopRefreshTimer');
        window.clearInterval(scheduler.aresTimeLineRefreshTimer);
        scheduler.aresTimeLineRefreshTimer = null;
    }
};

scheduler.aresRefreshTimeLinePosition = function () {
    //alert('aresRefreshTimeLinePosition');
    var now = new Date();
    var yOffset = scheduler._y_from_date(now);
    var top = yOffset + 'px';
    if (this.aresTimeLine) {
        this.aresTimeLine.css('top', top);
    }
};

scheduler.aresDelay = function (actionName, func, delayMillieconds) {
    //console.info('[scheduler], delay ' + actionName + ' for ' + delayMillieconds + 'ms');
    var timerName = 'timer_' + actionName;
    if (scheduler[timerName]) {
        window.clearTimeout(scheduler[timerName]);
        delete scheduler[timerName];
    }
    scheduler[timerName] = window.setTimeout(function () {
        window.clearTimeout(scheduler[timerName]);
        delete scheduler[timerName];
        func();
    }, delayMillieconds);
};

scheduler.aresDestroy = function () {
    this.aresStopRefreshTimer();
    this.clearAll();
};;
/*
dhtmlxScheduler v.4.1.0 Stardard

This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.

(c) Dinamenta, UAB.
*/
scheduler._props = {};
scheduler.createUnitsView = function (name, property, list, size, step, skip_incorrect) {
    if (typeof name == "object") {
        list = name.list;
        property = name.property;
        size = name.size || 0;
        step = name.step || 1;
        skip_incorrect = name.skip_incorrect;
        name = name.name;
    }

    scheduler._props[name] = { map_to: property, options: list, step: step, position: 0 };
    if (size > scheduler._props[name].options.length) {
        scheduler._props[name]._original_size = size;
        size = 0;
    }
    scheduler._props[name].size = size;
    scheduler._props[name].skip_incorrect = skip_incorrect || false;

    scheduler.date[name + "_start"] = scheduler.date.day_start;
    scheduler.templates[name + "_date"] = function (date) {
        return scheduler.templates.day_date(date);
    };

    scheduler._get_unit_index = function (unit_view, date) {
        var original_position = unit_view.position || 0;
        var date_position = Math.floor((scheduler._correct_shift(+date, 1) - +scheduler._min_date) / (60 * 60 * 24 * 1000));
        return original_position + date_position;
    };
    scheduler.templates[name + "_scale_text"] = function (id, label, option) {
        if (option.css) {
            return "<span class='" + option.css + "'>" + label + "</span>";
        } else {
            return label;
        }
    };
    scheduler.templates[name + "_scale_date"] = function (date) {
        var unit_view = scheduler._props[name];
        var list = unit_view.options;
        if (!list.length) return "";
        var index = scheduler._get_unit_index(unit_view, date);
        var option = list[index];
        return scheduler.templates[name + "_scale_text"](option.key, option.label, option);
    };

    scheduler.date["add_" + name] = function (date, inc) { return scheduler.date.add(date, inc, "day"); };
    scheduler.date["get_" + name + "_end"] = function (date) {
        return scheduler.date.add(date, scheduler._props[name].size || scheduler._props[name].options.length, "day");
    };

    if (scheduler.onOptionsLoadEventId) {
        scheduler.detachEvent(scheduler.onOptionsLoadEventId);
    }
    scheduler.onOptionsLoadEventId = scheduler.attachEvent("onOptionsLoad", function () {
        var pr = scheduler._props[name];
        var order = pr.order = {};
        var list = pr.options;
        for (var i = 0; i < list.length; i++)
            order[list[i].key] = i;
        if (pr._original_size && pr.size === 0) {
            pr.size = pr._original_size;
            delete pr.original_size;
        }
        if (pr.size > list.length) {
            pr._original_size = pr.size;
            pr.size = 0;
        }
        else
            pr.size = pr._original_size || pr.size;
        if (scheduler._date && scheduler._mode == name)
            scheduler.setCurrentView(scheduler._date, scheduler._mode);
    });

    scheduler["mouse_" + name] = function (pos) { //mouse_coord handler
        var pr = scheduler._props[this._mode];

        if (pr) {
            pos = this._week_indexes_from_pos(pos);
            if (!this._drag_event) this._drag_event = {};

            if (this._drag_id && this._drag_mode) {
                this._drag_event._dhx_changed = true;
            }
            var unit_ind = Math.min(pos.x + pr.position, pr.options.length - 1);
            pos.section = (pr.options[unit_ind] || {}).key;
            pos.x = 0;

            var ev = this.getEvent(this._drag_id);
            this._update_unit_section({ view: pr, event: ev, pos: pos });
        }
        pos.force_redraw = true;

        return pos;
    };

    // Added by Mason Start
    scheduler._events = [];
    // Added by Mason End

    scheduler.callEvent("onOptionsLoad", []);
};

scheduler.refreshUnitsView = function (name, list) {
    // Added by Mason Start
    scheduler._events = [];
    // Added by Mason End

    this._props[name].options = list;
    this.callEvent("onOptionsLoad", []);
};

scheduler._update_unit_section = function (action) {
    var view = action.view,
		event = action.event,
		pos = action.pos;
    if (event) {
        event[view.map_to] = pos.section;
    }
};

scheduler.scrollUnit = function (step) {
    var pr = scheduler._props[this._mode];
    if (pr) {
        pr.position = Math.min(Math.max(0, pr.position + step), pr.options.length - pr.size);
        this.update_view();
    }
};
(function () {
    var _removeIncorrectEvents = function (evs) {
        var pr = scheduler._props[scheduler._mode];
        if (pr && pr.order && pr.skip_incorrect) {
            var correct_events = [];
            for (var i = 0; i < evs.length; i++) {
                if (typeof pr.order[evs[i][pr.map_to]] != "undefined") {
                    correct_events.push(evs[i]);
                }
            }
            evs.splice(0, evs.length);
            evs.push.apply(evs, correct_events);
        }
        return evs;
    };
    var old_pre_render_events_table = scheduler._pre_render_events_table;
    scheduler._pre_render_events_table = function (evs, hold) {
        evs = _removeIncorrectEvents(evs);
        return old_pre_render_events_table.apply(this, [evs, hold]);
    };
    var old_pre_render_events_line = scheduler._pre_render_events_line;
    scheduler._pre_render_events_line = function (evs, hold) {
        evs = _removeIncorrectEvents(evs);
        return old_pre_render_events_line.apply(this, [evs, hold]);
    };
    var fix_und = function (pr, ev) {
        if (pr && typeof pr.order[ev[pr.map_to]] == "undefined") {
            var s = scheduler;
            var dx = 24 * 60 * 60 * 1000;
            var ind = Math.floor((ev.end_date - s._min_date) / dx);
            //ev.end_date = new Date(s.date.time_part(ev.end_date)*1000+s._min_date.valueOf());
            //ev.start_date = new Date(s.date.time_part(ev.start_date)*1000+s._min_date.valueOf());
            ev[pr.map_to] = pr.options[Math.min(ind + pr.position, pr.options.length - 1)].key;
            return true;
        }
    };
    var t = scheduler._reset_scale;

    var oldive = scheduler.is_visible_events;
    scheduler.is_visible_events = function (e) {
        var res = oldive.apply(this, arguments);
        if (res) {
            var pr = scheduler._props[this._mode];
            if (pr && pr.size) {
                var val = pr.order[e[pr.map_to]];
                if (val < pr.position || val >= pr.size + pr.position)
                    return false;
            }
        }
        return res;
    };
    scheduler._reset_scale = function () {
        var pr = scheduler._props[this._mode];
        var ret = t.apply(this, arguments);
        if (pr) {
            this._max_date = this.date.add(this._min_date, 1, "day");

            var d = this._els["dhx_cal_data"][0].childNodes;
            for (var i = 0; i < d.length; i++)
                d[i].className = d[i].className.replace("_now", ""); //clear now class

            if (pr.size && pr.size < pr.options.length) {

                var h = this._els["dhx_cal_header"][0];
                var arrow = document.createElement("DIV");
                if (pr.position) {
                    arrow.className = "dhx_cal_prev_button";
                    arrow.style.cssText = "left:1px;top:2px;position:absolute;";
                    arrow.innerHTML = "&nbsp;";
                    h.firstChild.appendChild(arrow);
                    arrow.onclick = function () {
                        scheduler.scrollUnit(pr.step * -1);
                    };
                }
                if (pr.position + pr.size < pr.options.length) {
                    arrow = document.createElement("DIV");
                    arrow.className = "dhx_cal_next_button";
                    arrow.style.cssText = "left:auto; right:0px;top:2px;position:absolute;";
                    arrow.innerHTML = "&nbsp;";
                    h.lastChild.appendChild(arrow);
                    arrow.onclick = function () {
                        scheduler.scrollUnit(pr.step);
                    };
                }
            }
        }
        return ret;

    };
    var r = scheduler._get_event_sday;
    scheduler._get_event_sday = function (ev) {
        var pr = scheduler._props[this._mode];
        if (pr) {
            fix_und(pr, ev);
            return pr.order[ev[pr.map_to]] - pr.position;
        }
        return r.call(this, ev);
    };
    var l = scheduler.locate_holder_day;
    scheduler.locate_holder_day = function (a, b, ev) {
        var pr = scheduler._props[this._mode];
        if (pr && ev) {
            fix_und(pr, ev);
            return pr.order[ev[pr.map_to]] * 1 + (b ? 1 : 0) - pr.position;
        }
        return l.apply(this, arguments);
    };

    var o = scheduler._time_order;
    scheduler._time_order = function (evs) {
        var pr = scheduler._props[this._mode];
        if (pr) {
            evs.sort(function (a, b) {
                return pr.order[a[pr.map_to]] > pr.order[b[pr.map_to]] ? 1 : -1;
            });
        } else
            o.apply(this, arguments);
    };
    scheduler.attachEvent("onEventAdded", function (id, ev) {
        if (this._loading) return true;
        for (var a in scheduler._props) {
            var pr = scheduler._props[a];
            if (typeof ev[pr.map_to] == "undefined")
                ev[pr.map_to] = pr.options[0].key;
        }
        return true;
    });
    scheduler.attachEvent("onEventCreated", function (id, n_ev) {
        var pr = scheduler._props[this._mode];
        if (pr && n_ev) {
            var ev = this.getEvent(id);
            var pos = this._mouse_coords(n_ev);
            this._update_unit_section({ view: pr, event: ev, pos: pos });
            fix_und(pr, ev);
            this.event_updated(ev);
        }
        return true;
    });
})();
;
(function (window) {
    Ares = window.Ares || {};

    Ares.template = function (key, value) {
        this.key = key;
        this.value = value;
    };

    Ares.templateCache = {
        templates: [],
        put: function (key, value) {
            this.templates.push(new Ares.template(key, value));
        }
    };

})(window);;
(function (window) {
	Ares = window.Ares || {};

	Ares.validateForm = function (formElement, rules, messages, ignore) {
		// Clear validator if existed.
		$.data(formElement[0], "validator", null);
		var validator = formElement.validate({
			ignore: ignore || '.ignore, :hidden:not(.not-ignore)',
			errorClass: 'has-error',
			rules: rules,
			messages: messages,
			errorPlacement: function (error, element) {
				Ares.setErrorStyle(error, element);
			},
			success: function (error, element) {
				Ares.clearErrorStyle(error, element);
			}
		});
		return validator;
	};

	Ares.setErrorStyle = function (error, element) {
		if (element.hasClass('has-error')) {
			var parentElement = element.parent();
			if (element.is(':checkbox')) {
				parentElement = parentElement.parent();
			}
			parentElement.addClass('has-error');
			// If element is absolution position, put the error element after its parent.
			if (element.css('position') == 'absolute') {
				error.insertAfter(parentElement);
			} else {
				error.insertAfter(element);
			}

			error.addClass('label');
			error.addClass('label-danger');
		}
	};
	Ares.clearErrorStyle = function (error, element) {
		var inputContainer = $(element).closest('.form-group');
		if (inputContainer.length == 0) {
			var inputContainer = $(element).closest('td');
		}
		inputContainer.find('.has-error').removeClass('has-error');
		inputContainer.find('.has-error').removeClass('has-error');
		inputContainer.removeClass('has-error');
		inputContainer.find('.label-danger').remove();
	};

	Ares.clearError = function (formElement) {
		var $formElement = $(formElement);
		var validator = $formElement.validate();
		if (validator) {
			validator.resetForm();
			validator.reset();
			$formElement.find('.has-error').removeClass('has-error');
		}
	};

	Ares.validator = {
		// mobilePattern: /^(13[0-9]{9})|(19[0-9]{9})|(18[0-9]{9})|(14[0-9]{9})|(17[0-9]{9})|(15[0-9]{9})$/,
		mobilePattern:/^1[0-9\*]{10}$/,
		validateMobile: function (value) {
			var length = value ? value.length : 0;
			var isValid = length == 11 && this.mobilePattern.test(value);
			return isValid;
		},
	};

})(window);

$(function () {
	jQuery.validator.addMethod("mobile", function (value, element) {
		return this.optional(element) || Ares.validator.validateMobile(value);
	}, "请输入正确的手机号码");

	jQuery.validator.addMethod("dateCn", function (value, element) {
		var isValid = true;
		if (value) {
			isValid = _.isString(value);
			if (isValid && value.length > 0) {
				var isValid = moment(value, 'YYYY-MM-DD', true).isValid();
			}
		}
		return isValid;
	}, "请输入正确的日期");

	jQuery.validator.addMethod("greaterThan", function (value, element, param) {
		if (value > param)
			return true;
		return false;
	}, "请输入正确的数字");
	jQuery.validator.addMethod("notEqualTo", function (value, element, param) {
		if (value != param)
			return true;
		return false;
	}, "请输入正确的数字");

	jQuery.validator.addMethod("passwordCn2", function (value, element) {
		var password = /^(?:(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])|(?=.*[A-Z])(?=.*[a-z])(?=.*[^A-Za-z0-9])|(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])|(?=.*[a-z])(?=.*[0-9])(?=.*[^A-Za-z0-9])).{6,}$/;
		return this.optional(element) || (password.test(value));
	}, "密码必须由数字,字母,特殊字符组成");

	jQuery.validator.addMethod("passwordCn", function (value, element) {
		var password = /^(?=.*?[a-zA-Z])(?=.*?[0-9])[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~A-Za-z0-9]{6,22}$/;
		return this.optional(element) || (password.test(value));
	}, "密码必须同时包含数字和字母");

	jQuery.validator.addMethod("NotIncludeBlank", function (value, element) {
		var reg = /^[^ ]+$/;
		if (reg.test(value)) {
			return true;
		} else {
			return false; //包含非法字符
		}
	}, "不能包含空格");

	jQuery.validator.addMethod("dateTimeCn", function (value, element) {
		var isValid = true;
		if (value) {
			isValid = _.isString(value);
			if (isValid && value.length > 0) {
				var isValid = moment(value, 'YYYY-MM-DD HH:mm', true).isValid();
			}
		}
		return isValid;
	}, "请输入正确的日期和时间");

	jQuery.validator.addMethod("contains", function (value, element, param) {
		var toBeContainedValue = $(param).val();
		var isValid = value == toBeContainedValue;
		if (!isValid && _.isArray(value)) {
			isValid = value.indexOf(toBeContainedValue) > -1;
		}
		if (!isValid && _.isString(value)) {
			var values = value.split(',');
			isValid = values.indexOf(toBeContainedValue) > -1;
		}
		return isValid;
	}, "没有包含指定的值");

	jQuery.validator.addMethod("dateMax", function (value, element, param) {
		var isValid = true;
		if (value) {
			isValid = _.isString(value);
			if (isValid && value.length > 0) {
				var theDate = moment(value, 'YYYY-MM-DD', true);
				var maxDate = null;
				if (_.isString(param)) {
					maxDate = momenet(param, 'YYYY-MM-DD').toDate();
				}
				// Default max date is today.
				maxDate = maxDate || new Date();
				isValid = theDate.isValid() && theDate.toDate() <= maxDate;

			}
		}
		return isValid;
	}, "日期超过允许日最大日期");

	jQuery.validator.addMethod("email", function (value, element, param) {
		var email = /\w[-\*\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/;
		return this.optional(element) || (email.test(value));
	}, "请输入正确的邮箱！");

    jQuery.validator.addMethod("identityCard", function (value, element, param) {
        var identityCard = /(^[1|5|7][0-9]{6}\(\w{1}\)$)|(^((\s?[A-Za-z])|([A-Za-z]{2}))\d{6}(\(\w{1}\)|\w{1})$)|(^\d{8}$)|(^\d{10}$)|(^\d{15}$)|(^\d{17}(\d|x|X)$)|(^\d{4}\*+\d{3}(\d|x|X)$)/;
		return this.optional(element) || (identityCard.test(value));
	}, "请输入正确的身份证号码！");
});;
(function (window) {
    Ares = window.Ares || {};
    Ares.enableDebug = function () {
        if (localStorage) {
            localStorage['AresDebug'] = 'TRUE';
            console.info('debug enabled');
        }
    };
    Ares.disableDebug = function () {
        if (localStorage) {
            localStorage.removeItem('AresDebug');
            console.info('debug disabled');
        }
    };
    Ares.isDebugEnabled = function () {
        return localStorage['AresDebug'] === 'TRUE';
    };

})(window);;
(function (window) {
    Ares = window.Ares || {};

    Ares.logOffUrl = '/logoff?platform=1';

    Ares.getClientId = function () {
        var clientId = $.cookie("ClientId") || null;
        return clientId;
    };

    Ares.initLogOffUrl = function () {
        var clientId = Ares.getClientId();
        var url = '/logoff?platform=1&clientId=' + clientId;
        Ares.logOffUrl = url;
        Ares.logOffUrlAjax = url + '&signOut=false';
    };

    Ares.disableAutoLogOff = function () {
        Ares.autoLogOffDisabled = true;
    };

    Ares.logOn = function (logOnResult) {
        var logOnResultStr = JSON.stringify(logOnResult);
        if (window.localStorage) {
            window.localStorage.setItem('LogOnResult', logOnResultStr);
            if (logOnResult.User) {
                window.localStorage.setItem('lastLoginOffice', logOnResult.LoginOfficeId);
                window.localStorage.setItem('lastLoginOfficeName', logOnResult.OfficeName);
                // 记录登录时间
                var lastLoginTimeStorageKey = btoa(btoa('LastLoginTime'));
                var lastLoginTimeValue = btoa(btoa(new Date().getTime()));
                localStorage.setItem(lastLoginTimeStorageKey, lastLoginTimeValue);
            }
        } else {
            $.cookie('LogOnResult', logOnResultStr);
        }
        if (logOnResult.State === 0) {
            var indexUrl = "/";
            if (logOnResult && (logOnResult.IsAres3Enabled  || logOnResult.IsAres2Disabled)) {
                indexUrl = '/ares3/';
            }
            window.location = indexUrl;
        }
    };

    Ares.logOff = function (isWindowUnloaded) {        
        if (!isWindowUnloaded || window.closed) {
            if (sessionStorage) {
                sessionStorage.clear();
            }
            if (window.localStorage) {
                window.localStorage.removeItem('AresToken');
                window.localStorage.removeItem('LogOnResult');
            } else {
                $.removeCookie('AresToken');
                $.removeCookie('LogOnResult');
            }
            Ares.disableAutoLogOff();
            window.location = Ares.logOffUrl;
        } else {
            if (!Ares.autoLogOffDisabled) {
                $.ajax({
                    type: 'Get',
                    url: Ares.logOffUrlAjax,
                });
            }
        }
    };
})(window);
;
// 只有当在安卓平板下的 chrome 中才执行
if (!!window.navigator.userAgent.match(/(Android.*Chrome)|AppleWebkit/i)) {
    // 为了处理 mobile chrome vh 计算方式问题
    var vhTimer = null;
    var setVh = function() {
        setTimeout(function () {
            var vh = window.innerHeight * 0.01;
            document.documentElement.style.setProperty('--vh', `${vh}px`);
        });
    };
    window.addEventListener('resize', function () {
        if (vhTimer) {
            clearTimeout(vhTimer);
        }
        vhTimer = setTimeout(function () {
            setVh();
        }, 300);
    });
    setVh();
}
;
(function (window) {
    Ares = window.Ares || {};

    Ares.download = function (downloadUrl) {
        window.isDownloading = true;
        window.location.assign(downloadUrl);
    };

    Ares.downloadFile = function (data, fileName) {
        var file = new Blob([data], { type: 'application/octet-stream' });
        var url = window.URL || window.webkitURL
        var fileURL = url.createObjectURL(file);
        var a = document.createElement('a');
        a.href = fileURL;
        a.target = '_blank';
        a.download = fileName;
        document.body.appendChild(a);
        a.click();
    };

})(window);;
